diff --git a/src/components/Dashboard/Agents/AgentCardList.tsx b/src/components/Dashboard/Agents/AgentCardList.tsx index 3cbc439d..f0fd84fa 100644 --- a/src/components/Dashboard/Agents/AgentCardList.tsx +++ b/src/components/Dashboard/Agents/AgentCardList.tsx @@ -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[]; @@ -14,7 +16,15 @@ type Props = { }; export function AgentCardList({ agents, count, columns, rows, currentPage, setCurrentPage, districtsList }: Props) { - const items = agents.map((agent) => ); + const { isAuthorized } = useAuth(); + + const items = agents.map((agent) => + isAuthorized ? ( + + ) : ( + + ), + ); return ( diff --git a/src/components/Dashboard/Agents/AgentReadOnlyCard.tsx b/src/components/Dashboard/Agents/AgentReadOnlyCard.tsx new file mode 100644 index 00000000..24c7d270 --- /dev/null +++ b/src/components/Dashboard/Agents/AgentReadOnlyCard.tsx @@ -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 ( + + + + {title} + + + + {t("dashboard.agentProfile.type")} + {type} + + + + + + + {districtTitle} + + + + ); +}; diff --git a/src/components/Dashboard/Agents/AgentReadOnlyTableRow.tsx b/src/components/Dashboard/Agents/AgentReadOnlyTableRow.tsx new file mode 100644 index 00000000..9f3b7113 --- /dev/null +++ b/src/components/Dashboard/Agents/AgentReadOnlyTableRow.tsx @@ -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; + searchLabels: Record; + 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 ( + + {title} + {type} + {districtTitle || "—"} + + ); +} diff --git a/src/components/Dashboard/Agents/AgentTableList.tsx b/src/components/Dashboard/Agents/AgentTableList.tsx index 2a428c54..7f24cfdd 100644 --- a/src/components/Dashboard/Agents/AgentTableList.tsx +++ b/src/components/Dashboard/Agents/AgentTableList.tsx @@ -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[]; @@ -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 ( ( - - )} + renderRow={(agent, isLast) => + isAuthorized ? ( + + ) : ( + + ) + } count={count} itemsPerPage={itemsPerPage} currentPage={currentPage} setCurrentPage={setCurrentPage} testIdPrefix="agents" + noFixedWidth={!isAuthorized} /> ); } diff --git a/src/components/Dashboard/Agents/Filters/FiltersContent.tsx b/src/components/Dashboard/Agents/Filters/FiltersContent.tsx index 567c0d92..89a22f27 100644 --- a/src/components/Dashboard/Agents/Filters/FiltersContent.tsx +++ b/src/components/Dashboard/Agents/Filters/FiltersContent.tsx @@ -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; @@ -12,6 +13,7 @@ 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); @@ -19,7 +21,9 @@ export default function FiltersContent({ setFilter, filter }: Props) { return ( - + {isAuthorized && ( + + )} diff --git a/src/components/Dashboard/Agents/agentsTableColumns.ts b/src/components/Dashboard/Agents/agentsTableColumns.ts index 95a5db20..23686f40 100644 --- a/src/components/Dashboard/Agents/agentsTableColumns.ts +++ b/src/components/Dashboard/Agents/agentsTableColumns.ts @@ -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 }, @@ -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 }, +]; diff --git a/src/components/Dashboard/Agents/styles.ts b/src/components/Dashboard/Agents/styles.ts index 08a069a9..73422bb8 100644 --- a/src/components/Dashboard/Agents/styles.ts +++ b/src/components/Dashboard/Agents/styles.ts @@ -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); @@ -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); diff --git a/src/components/Dashboard/Opportunities/Filters/FiltersContent.tsx b/src/components/Dashboard/Opportunities/Filters/FiltersContent.tsx index 016febbd..a42dcc3c 100644 --- a/src/components/Dashboard/Opportunities/Filters/FiltersContent.tsx +++ b/src/components/Dashboard/Opportunities/Filters/FiltersContent.tsx @@ -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; @@ -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); @@ -22,11 +24,13 @@ export default function FiltersContent({ setFilter, filter }: Props) { - + {isAuthorized && ( + + )} ); } diff --git a/src/components/Dashboard/Opportunities/OpportunityCardList.tsx b/src/components/Dashboard/Opportunities/OpportunityCardList.tsx index 6e825bc5..17916f8b 100644 --- a/src/components/Dashboard/Opportunities/OpportunityCardList.tsx +++ b/src/components/Dashboard/Opportunities/OpportunityCardList.tsx @@ -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[]; @@ -26,15 +28,21 @@ export function OpportunityCardList({ activitiesList, districtsList, }: Props) { - const items = opportunities.map((opp) => ( - - )); + const { isAuthorized } = useAuth(); + + const items = opportunities.map((opp) => + isAuthorized ? ( + + ) : ( + + ), + ); return ( diff --git a/src/components/Dashboard/Opportunities/OpportunityReadOnlyCard.tsx b/src/components/Dashboard/Opportunities/OpportunityReadOnlyCard.tsx new file mode 100644 index 00000000..566bce18 --- /dev/null +++ b/src/components/Dashboard/Opportunities/OpportunityReadOnlyCard.tsx @@ -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 ( + + + {statusMatch && ( + + {matchStatusIconMap[statusMatch]} + + {t(`dashboard.opportunities.matchStatus.${statusMatch}`)} + + + )} + {volunteerType && ( + + + {t(`dashboard.opportunities.type.${volunteerType}`)} + + {volunteerTypeIconMap[volunteerType as ProfileVolunteeringType]} + + )} + + + {title} + + + {mainCommunication && ( + + + + + )} + {recipientLanguage && ( + + + + + )} + + + + {districtTitle && } + + + ); +} diff --git a/src/components/Dashboard/Opportunities/OpportunityReadOnlyTableRow.tsx b/src/components/Dashboard/Opportunities/OpportunityReadOnlyTableRow.tsx new file mode 100644 index 00000000..b6672f08 --- /dev/null +++ b/src/components/Dashboard/Opportunities/OpportunityReadOnlyTableRow.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { LangPurpose, type ApiVolunteerOpportunityGetList, type OptionItem } from "need4deed-sdk"; +import { useTranslation } from "react-i18next"; +import { ClickableRow, TableCell } from "@/components/core/common/Table"; +import { OPPORTUNITY_READ_ONLY_COL_WIDTHS } from "./opportunitiesTableColumns"; +import { getLanguagesByPurpose } from "./helpers"; + +interface TableRowProps { + opportunity: ApiVolunteerOpportunityGetList; + isLast: boolean; + activitiesList?: OptionItem[]; + districtsList?: OptionItem[]; +} + +export function OpportunityReadOnlyTableRow({ opportunity, isLast, districtsList }: TableRowProps) { + const { t } = useTranslation(); + + const { id, title, volunteerType, district, statusMatch, languages } = opportunity; + const mainCommunication = getLanguagesByPurpose(languages, LangPurpose.GENERAL); + const districtTitle = district?.id ? (districtsList?.find((d) => d.id === district.id)?.title ?? null) : null; + return ( + + + {title} + + + {t(`dashboard.opportunities.type.${volunteerType}`)} + + + {t(`dashboard.opportunities.matchStatus.${statusMatch}`)} + + + {mainCommunication || "—"} + + + {districtTitle || "—"} + + + ); +} diff --git a/src/components/Dashboard/Opportunities/OpportunityTableList.tsx b/src/components/Dashboard/Opportunities/OpportunityTableList.tsx index ce8b296a..0f929015 100644 --- a/src/components/Dashboard/Opportunities/OpportunityTableList.tsx +++ b/src/components/Dashboard/Opportunities/OpportunityTableList.tsx @@ -4,8 +4,10 @@ import type { ApiVolunteerOpportunityGetList, OptionItem } from "need4deed-sdk"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { EntityTableList } from "../common/EntityTableList"; -import { createOpportunityTableColumns } from "./opportunitiesTableColumns"; +import { createOpportunityTableColumns, createReadOnlyAgentTableColumns } from "./opportunitiesTableColumns"; import { OpportunityTableRow } from "./OpportunityTableRow"; +import { OpportunityReadOnlyTableRow } from "./OpportunityReadOnlyTableRow"; +import { useAuth } from "@/hooks/useAuth"; interface TableListProps { opportunities: ApiVolunteerOpportunityGetList[]; @@ -27,22 +29,32 @@ export function OpportunityTableList({ activitiesList, }: TableListProps) { const { t } = useTranslation(); + const { isAuthorized } = useAuth(); const columns = useMemo(() => createOpportunityTableColumns(t), [t]); - + const readOnlyColumns = useMemo(() => createReadOnlyAgentTableColumns(t), [t]); return ( ( - - )} + renderRow={(opportunity, isLast) => + isAuthorized ? ( + + ) : ( + + ) + } count={count} itemsPerPage={itemsPerPage} currentPage={currentPage} diff --git a/src/components/Dashboard/Opportunities/opportunitiesTableColumns.ts b/src/components/Dashboard/Opportunities/opportunitiesTableColumns.ts index 1eb64cb1..c2d07c1a 100644 --- a/src/components/Dashboard/Opportunities/opportunitiesTableColumns.ts +++ b/src/components/Dashboard/Opportunities/opportunitiesTableColumns.ts @@ -15,6 +15,14 @@ export const OPPORTUNITY_COL_WIDTHS = { agentTitle: COLUMN_WIDTH.MD, }; +export const OPPORTUNITY_READ_ONLY_COL_WIDTHS = { + title: COLUMN_WIDTH.XXXL, + volunteerType: COLUMN_WIDTH.XXXL, + statusMatch: COLUMN_WIDTH.XXXL, + languages: COLUMN_WIDTH.XXXL, + district: COLUMN_WIDTH.XXXL, +}; + export const createOpportunityTableColumns = (t: TFunction): Column[] => [ { key: "title", label: t("dashboard.opportunities.table.title"), width: OPPORTUNITY_COL_WIDTHS.title }, { key: "volunteerType", label: t("dashboard.opportunities.table.type"), width: OPPORTUNITY_COL_WIDTHS.volunteerType }, @@ -39,3 +47,19 @@ export const createOpportunityTableColumns = (t: TFunction): Column[] => [ }, { key: "agentTitle", label: t("dashboard.opportunities.table.agentName"), width: OPPORTUNITY_COL_WIDTHS.agentTitle }, ]; + +export const createReadOnlyAgentTableColumns = (t: TFunction): Column[] => [ + { key: "title", label: t("dashboard.agents.table.title"), width: OPPORTUNITY_READ_ONLY_COL_WIDTHS.title }, + { key: "type", label: t("dashboard.agents.table.type"), width: OPPORTUNITY_READ_ONLY_COL_WIDTHS.volunteerType }, + { + key: "statusMatch", + label: t("dashboard.opportunities.table.matchingStatus"), + width: OPPORTUNITY_READ_ONLY_COL_WIDTHS.statusMatch, + }, + { + key: "languages", + label: t("dashboard.opportunities.table.languages"), + width: OPPORTUNITY_READ_ONLY_COL_WIDTHS.languages, + }, + { key: "district", label: t("dashboard.agents.table.district"), width: OPPORTUNITY_READ_ONLY_COL_WIDTHS.district }, +]; diff --git a/src/components/Dashboard/Opportunities/styles.ts b/src/components/Dashboard/Opportunities/styles.ts index 8d146bb5..2192afae 100644 --- a/src/components/Dashboard/Opportunities/styles.ts +++ b/src/components/Dashboard/Opportunities/styles.ts @@ -24,7 +24,7 @@ export const OpportunityCardListContainer = styled.div` justify-content: left; `; -export const Card = styled(BaseCard)` +export const Card = styled(BaseCard)<{ $cursor?: string }>` background-color: var(--color-orchid-subtle); width: var(--dashboard-volunteers-card-width); min-height: var(--dashboard-volunteers-card-height); @@ -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); diff --git a/src/components/Dashboard/common/EntityTableList/EntityTableList.tsx b/src/components/Dashboard/common/EntityTableList/EntityTableList.tsx index eb96b422..4617e8eb 100644 --- a/src/components/Dashboard/common/EntityTableList/EntityTableList.tsx +++ b/src/components/Dashboard/common/EntityTableList/EntityTableList.tsx @@ -14,6 +14,7 @@ export function EntityTableList({ currentPage, setCurrentPage, testIdPrefix, + noFixedWidth = false, }: EntityTableListProps) { const totalPages = Math.ceil(count / itemsPerPage); const goToPage = (page: number) => { @@ -26,7 +27,7 @@ export function EntityTableList({ {columns.map((col) => ( - + {col.label} {col.headerAction} diff --git a/src/components/Dashboard/common/EntityTableList/types.ts b/src/components/Dashboard/common/EntityTableList/types.ts index 4302e0bb..de04e214 100644 --- a/src/components/Dashboard/common/EntityTableList/types.ts +++ b/src/components/Dashboard/common/EntityTableList/types.ts @@ -16,4 +16,5 @@ export interface EntityTableListProps { currentPage: number; setCurrentPage: (page: number) => void; testIdPrefix: string; + noFixedWidth?: boolean; } diff --git a/src/components/core/common/Table/styles.ts b/src/components/core/common/Table/styles.ts index 6a3f77e2..259c5bdb 100644 --- a/src/components/core/common/Table/styles.ts +++ b/src/components/core/common/Table/styles.ts @@ -105,8 +105,9 @@ export const ActionButton = styled.button` } `; -export const ClickableRow = styled(TableRow)` - cursor: pointer; +export const ClickableRow = styled(TableRow)<{ $cursor?: string }>` + cursor: ${(props) => props.$cursor || "pointer"}; + &:hover { background: var(--color-pink-50); }