Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/components/Dashboard/Agents/AgentCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { ApiAgentGetList, OptionItem } from "need4deed-sdk";
import { PaginatedGrid } from "@/components/core/paginatedGrid";
import { AgentCard } from "./AgentCard";
import { AgentCardListContainer } from "./styles";
import { AgentReadOnlyCard } from "./AgentReadOnlyCard";
import { useAuth } from "@/hooks/useAuth";

type Props = {
agents: ApiAgentGetList[];
Expand All @@ -14,7 +16,15 @@ type Props = {
};

export function AgentCardList({ agents, count, columns, rows, currentPage, setCurrentPage, districtsList }: Props) {
const items = agents.map((agent) => <AgentCard key={agent.id} agent={agent} districtsList={districtsList} />);
const { isAuthorized } = useAuth();

const items = agents.map((agent) =>
isAuthorized ? (
<AgentCard key={agent.id} agent={agent} districtsList={districtsList} />
) : (
<AgentReadOnlyCard key={agent.id} agent={agent} districtsList={districtsList} />
),
);

return (
<AgentCardListContainer data-testid="agent-card-list">
Expand Down
38 changes: 38 additions & 0 deletions src/components/Dashboard/Agents/AgentReadOnlyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { MapPinIcon } from "@phosphor-icons/react";
import { IconDiv } from "@/components/styled/container";
import { Heading4, Paragraph } from "@/components/styled/text";
import { Card, CardDetailsInfo, CardHeader, CardHeaderInfo, DistrictContainer, DistrictDiv } from "./styles";
import { useTranslation } from "react-i18next";
import { ApiAgentGetList, OptionItem } from "need4deed-sdk";

interface Props {
agent: ApiAgentGetList;
districtsList?: OptionItem[];
}

export const AgentReadOnlyCard = ({ agent, districtsList }: Props) => {
const { t } = useTranslation();
const { title, district, type } = agent;
const districtTitle = district?.id ? (districtsList?.find((d) => d.id === district.id)?.title ?? null) : null;
return (
<Card $cursor="auto">
<CardHeader>
<CardHeaderInfo>
<Heading4>{title}</Heading4>
</CardHeaderInfo>
</CardHeader>
<CardDetailsInfo>
<Heading4>{t("dashboard.agentProfile.type")}</Heading4>
<Paragraph> {type}</Paragraph>
</CardDetailsInfo>
<DistrictContainer>
<DistrictDiv>
<IconDiv size="var(--dashboard-agents-card-detail-icon-size)">
<MapPinIcon weight="fill" />
</IconDiv>
<Paragraph>{districtTitle}</Paragraph>
</DistrictDiv>
</DistrictContainer>
</Card>
);
};
22 changes: 22 additions & 0 deletions src/components/Dashboard/Agents/AgentReadOnlyTableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ApiAgentGetList, OptionItem } from "need4deed-sdk";
import { ClickableRow, TableCell } from "@/components/core/common/Table";

interface Props {
agent: ApiAgentGetList;
isLast: boolean;
typeLabels: Record<string, string>;
searchLabels: Record<string, string>;
districtsList?: OptionItem[];
}

export function AgentReadOnlyTableRow({ agent, isLast, districtsList }: Props) {
const { id, title, type, district } = agent;
const districtTitle = district?.id ? (districtsList?.find((d) => d.id === district.id)?.title ?? null) : null;
return (
<ClickableRow $isLast={isLast} $cursor={"auto"} data-testid={`agent-row-${id}`}>
<TableCell data-testid={`agent-title-${id}`}>{title}</TableCell>
<TableCell data-testid={`agent-type-${id}`}>{type}</TableCell>
<TableCell data-testid={`agent-district-${id}`}>{districtTitle || "—"}</TableCell>
</ClickableRow>
);
}
40 changes: 28 additions & 12 deletions src/components/Dashboard/Agents/AgentTableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import type { ApiAgentGetList, OptionItem } from "need4deed-sdk";
import { EntityTableList } from "../common/EntityTableList";
import { useTranslation } from "react-i18next";
import { createAgentTableColumns } from "./agentsTableColumns";
import { createAgentTableColumns, createReadOnlyAgentTableColumns } from "./agentsTableColumns";
import { useMemo } from "react";
import { AgentTableRow } from "./AgentTableRow";
import { createAgentTypeMap, createVolunteerSearchMap } from "./constants";
import { AgentReadOnlyTableRow } from "./AgentReadOnlyTableRow";
import { useAuth } from "@/hooks/useAuth";

interface TableListProps {
agents: ApiAgentGetList[];
Expand All @@ -26,30 +28,44 @@ export function AgentTableList({
districtsList,
}: TableListProps) {
const { t } = useTranslation();
const { isAuthorized } = useAuth();

const columns = useMemo(() => createAgentTableColumns(t), [t]);
const readOnlyColumns = useMemo(() => createReadOnlyAgentTableColumns(t), [t]);
const typeLabels = useMemo(() => createAgentTypeMap(t), [t]);
const searchLabels = useMemo(() => createVolunteerSearchMap(t), [t]);

return (
<EntityTableList
columns={columns}
columns={isAuthorized ? columns : readOnlyColumns}
data={agents}
renderRow={(agent, isLast) => (
<AgentTableRow
key={agent.id}
agent={agent}
isLast={isLast}
typeLabels={typeLabels}
searchLabels={searchLabels}
districtsList={districtsList}
/>
)}
renderRow={(agent, isLast) =>
isAuthorized ? (
<AgentTableRow
key={agent.id}
agent={agent}
isLast={isLast}
typeLabels={typeLabels}
searchLabels={searchLabels}
districtsList={districtsList}
/>
) : (
<AgentReadOnlyTableRow
key={agent.id}
agent={agent}
isLast={isLast}
typeLabels={typeLabels}
searchLabels={searchLabels}
districtsList={districtsList}
/>
)
}
count={count}
itemsPerPage={itemsPerPage}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
testIdPrefix="agents"
noFixedWidth={!isAuthorized}
/>
);
}
6 changes: 5 additions & 1 deletion src/components/Dashboard/Agents/Filters/FiltersContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AccordionFilter from "../../common/CardsFilter/AccordionFilter";
import { SetFilter } from "../../common/CardsFilter/types";
import { createAgentFilterItems } from "./helpers";
import { FiltersContentContainer } from "./styles";
import { useAuth } from "@/hooks/useAuth";

type Props = {
filter: AgentCardsFilter;
Expand All @@ -12,14 +13,17 @@ type Props = {

export default function FiltersContent({ setFilter, filter }: Props) {
const { t } = useTranslation();
const { isAuthorized } = useAuth();

const { districtFilters, volunteerSearchFilters, typeFilters, engagementStatusFilters, servicesFilters } =
createAgentFilterItems(filter, setFilter, t);

return (
<FiltersContentContainer data-testid="agent-filters-content">
<AccordionFilter header={t("dashboard.agents.filters.type.header")} items={typeFilters} />
<AccordionFilter header={t("dashboard.agents.filters.volunteerSearch.header")} items={volunteerSearchFilters} />
{isAuthorized && (
<AccordionFilter header={t("dashboard.agents.filters.volunteerSearch.header")} items={volunteerSearchFilters} />
)}
<AccordionFilter header={t("dashboard.agents.filters.district.header")} items={districtFilters} />
<AccordionFilter header={t("dashboard.agents.filters.engagementStatus.header")} items={engagementStatusFilters} />
<AccordionFilter header={t("dashboard.agents.filters.services.header")} items={servicesFilters} />
Expand Down
12 changes: 12 additions & 0 deletions src/components/Dashboard/Agents/agentsTableColumns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export const AGENT_COL_WIDTHS = {
email: COLUMN_WIDTH.LG,
};

export const AGENT_READ_ONLY_COL_WIDTHS = {
title: COLUMN_WIDTH.XXXL,
type: COLUMN_WIDTH.XXXL,
district: COLUMN_WIDTH.XXXL,
};

export const createAgentTableColumns = (t: TFunction): Column[] => [
{ key: "title", label: t("dashboard.agents.table.title") },
{ key: "type", label: t("dashboard.agents.table.type"), width: AGENT_COL_WIDTHS.type },
Expand All @@ -32,3 +38,9 @@ export const createAgentTableColumns = (t: TFunction): Column[] => [
},
{ key: "email", label: t("dashboard.agents.table.email") },
];

export const createReadOnlyAgentTableColumns = (t: TFunction): Column[] => [
{ key: "title", label: t("dashboard.agents.table.title"), width: AGENT_READ_ONLY_COL_WIDTHS.title },
{ key: "type", label: t("dashboard.agents.table.type"), width: AGENT_READ_ONLY_COL_WIDTHS.type },
{ key: "district", label: t("dashboard.agents.table.district"), width: AGENT_READ_ONLY_COL_WIDTHS.district },
];
4 changes: 2 additions & 2 deletions src/components/Dashboard/Agents/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const AgentCardListContainer = styled.div`
justify-content: left;
`;

export const Card = styled(BaseCard)`
export const Card = styled(BaseCard)<{ $cursor?: string }>`
background-color: var(--color-orchid-subtle);
background-color: var(--color-orchid-subtle);
width: var(--dashboard-agents-card-width);
Expand All @@ -33,7 +33,7 @@ export const Card = styled(BaseCard)`
transition:
transform 0.3s ease-in-out,
box-shadow 0.3s ease-in-out;
cursor: pointer;
cursor: ${(props) => props.$cursor || "pointer"};

&:hover {
background-color: var(--color-orchid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import AccordionFilter from "../../common/CardsFilter/AccordionFilter";
import { SetFilter } from "../../common/CardsFilter/types";
import { createOpportunityFilterItems } from "./helpers";
import { FiltersContentContainer } from "./styles";
import { useAuth } from "@/hooks/useAuth";

type Props = {
filter: OpportunityCardsFilter;
Expand All @@ -12,6 +13,7 @@ type Props = {

export default function FiltersContent({ setFilter, filter }: Props) {
const { t } = useTranslation();
const { isAuthorized } = useAuth();

const { districtFilters, languageFilters, statusFilters, typeFilters, activityFilters, availabilityFilters } =
createOpportunityFilterItems(filter, setFilter, t);
Expand All @@ -22,11 +24,13 @@ export default function FiltersContent({ setFilter, filter }: Props) {
<AccordionFilter header={t("dashboard.volunteers.filters.district")} items={districtFilters} />
<AccordionFilter header={t("dashboard.volunteers.filters.languages")} items={languageFilters} />
<AccordionFilter header={t("dashboard.volunteers.filters.activities")} items={activityFilters} />
<AccordionFilter
header={t("dashboard.opportunities.filters.schedule.header")}
groupedItems={availabilityFilters}
groupedItemsDisplayType="button"
/>
{isAuthorized && (
<AccordionFilter
header={t("dashboard.opportunities.filters.schedule.header")}
groupedItems={availabilityFilters}
groupedItemsDisplayType="button"
/>
)}
</FiltersContentContainer>
);
}
26 changes: 17 additions & 9 deletions src/components/Dashboard/Opportunities/OpportunityCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ApiVolunteerOpportunityGetList, OptionItem } from "need4deed-sdk";
import { PaginatedGrid } from "@/components/core/paginatedGrid";
import { OpportunityCard } from "./OpportunityCard";
import { OpportunityCardListContainer } from "./styles";
import { OpportunityReadOnlyCard } from "./OpportunityReadOnlyCard";
import { useAuth } from "@/hooks/useAuth";

type Props = {
activitiesList?: OptionItem[];
Expand All @@ -26,15 +28,21 @@ export function OpportunityCardList({
activitiesList,
districtsList,
}: Props) {
const items = opportunities.map((opp) => (
<OpportunityCard
key={opp.id}
opportunity={opp}
volunteerId={volunteerId}
activitiesList={activitiesList}
districtsList={districtsList}
/>
));
const { isAuthorized } = useAuth();

const items = opportunities.map((opp) =>
isAuthorized ? (
<OpportunityCard
key={opp.id}
opportunity={opp}
volunteerId={volunteerId}
activitiesList={activitiesList}
districtsList={districtsList}
/>
) : (
<OpportunityReadOnlyCard key={opp.id} opportunity={opp} districtsList={districtsList} />
),
);

return (
<OpportunityCardListContainer data-testid="opportunity-card-list">
Expand Down
83 changes: 83 additions & 0 deletions src/components/Dashboard/Opportunities/OpportunityReadOnlyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ApiVolunteerOpportunityGetList, LangPurpose, OptionItem, ProfileVolunteeringType } from "need4deed-sdk";
import { useTranslation } from "react-i18next";
import { Paragraph } from "@/components/styled/text";
import CardDetail from "../Volunteers/CardDetail";
import { CardParagraph } from "../Volunteers/VolunteerCard";
import { IconName } from "../Volunteers/icon";
import { matchStatusColorMap, matchStatusIconMap, volunteerTypeIconMap } from "./OpportunityCard.helpers";
import { Card, LanguageRow, StatusDiv, StatusTagsDiv, TagDiv, TitleParagraph } from "./styles";
import { getLanguagesByPurpose } from "./helpers";

type Props = {
opportunity: ApiVolunteerOpportunityGetList;
volunteerId?: string;
activitiesList?: OptionItem[];
districtsList?: OptionItem[];
};

export function OpportunityReadOnlyCard({ opportunity, districtsList }: Props) {
const { t } = useTranslation();

const { title, volunteerType, district, languages, statusMatch } = opportunity as ApiVolunteerOpportunityGetList & {
accompanyingDetails?: { appointmentDate?: string; appointmentTime?: string };
statusMatch?: string;
district?: { id: number };
};

const mainCommunication = getLanguagesByPurpose(languages, LangPurpose.GENERAL);
const recipientLanguage = getLanguagesByPurpose(languages, LangPurpose.RECIPIENT);

const districtTitle = district?.id ? (districtsList?.find((d) => d.id === district.id)?.title ?? null) : null;
return (
<Card data-testid="opportunity-card" $cursor={"auto"}>
<StatusTagsDiv>
{statusMatch && (
<StatusDiv>
{matchStatusIconMap[statusMatch]}
<Paragraph
fontWeight="var(--dashboard-volunteers-card-status-fontWeight)"
fontSize="var(--dashboard-volunteers-card-status-fontSize)"
lineheight="var(--dashboard-volunteers-card-status-lineHeight)"
color={matchStatusColorMap[statusMatch]}
>
{t(`dashboard.opportunities.matchStatus.${statusMatch}`)}
</Paragraph>
</StatusDiv>
)}
{volunteerType && (
<TagDiv>
<Paragraph
fontWeight="var(--dashboard-volunteers-card-tag-fontWeight)"
fontSize="var(--dashboard-volunteers-card-status-fontSize)"
lineheight="var(--dashboard-volunteers-card-tag-lineHeight)"
>
{t(`dashboard.opportunities.type.${volunteerType}`)}
</Paragraph>
{volunteerTypeIconMap[volunteerType as ProfileVolunteeringType]}
</TagDiv>
)}
</StatusTagsDiv>

<TitleParagraph>{title}</TitleParagraph>

<CardDetail header={t("dashboard.volunteers.languages")} iconName={IconName.Translate}>
{mainCommunication && (
<LanguageRow>
<CardParagraph text={`${t("dashboard.opportunities.card.mainCommunication")}:`} isBold />
<CardParagraph text={mainCommunication} />
</LanguageRow>
)}
{recipientLanguage && (
<LanguageRow>
<CardParagraph text={`${t("dashboard.opportunities.card.residentsSpeak")}:`} isBold />
<CardParagraph text={recipientLanguage} />
</LanguageRow>
)}
</CardDetail>

<CardDetail header={t("dashboard.opportunities.district")} iconName={IconName.MapPin}>
{districtTitle && <CardParagraph text={districtTitle} />}
</CardDetail>
</Card>
);
}
Loading
Loading