From 5cd9327dab4d27fb662ffebbb1c24d5d7dfc9988 Mon Sep 17 00:00:00 2001 From: nadavosa Date: Mon, 11 May 2026 19:03:33 +0200 Subject: [PATCH 1/6] fix: add volunteer type filter to volunteer search Closes https://github.com/need4deed-org/fe/issues/369 Co-Authored-By: Claude Sonnet 4.6 --- .../Dashboard/Volunteers/Filters/FiltersContent.tsx | 3 ++- .../Dashboard/Volunteers/Filters/constants.tsx | 8 +++++++- .../Dashboard/Volunteers/Filters/helpers.ts | 13 ++++++++++--- .../Dashboard/Volunteers/Filters/types.ts | 1 + src/components/Dashboard/Volunteers/helpers.ts | 12 ++++++++++++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/components/Dashboard/Volunteers/Filters/FiltersContent.tsx b/src/components/Dashboard/Volunteers/Filters/FiltersContent.tsx index 25641881..ad38841b 100644 --- a/src/components/Dashboard/Volunteers/Filters/FiltersContent.tsx +++ b/src/components/Dashboard/Volunteers/Filters/FiltersContent.tsx @@ -15,7 +15,7 @@ interface Props { export default function FiltersContent({ setFilter, filter }: Props) { const { t } = useTranslation(); - const { availabilityFilters, districtFilters, engagementFilters, languageFilters, accompanyingFilter } = + const { availabilityFilters, districtFilters, engagementFilters, languageFilters, accompanyingFilter, typeFilters } = createFilterItems(filter, setFilter, t); return ( @@ -43,6 +43,7 @@ export default function FiltersContent({ setFilter, filter }: Props) { + diff --git a/src/components/Dashboard/Volunteers/Filters/constants.tsx b/src/components/Dashboard/Volunteers/Filters/constants.tsx index 187b05e3..2d4b5d91 100644 --- a/src/components/Dashboard/Volunteers/Filters/constants.tsx +++ b/src/components/Dashboard/Volunteers/Filters/constants.tsx @@ -1,9 +1,15 @@ -import { ByDay, OccasionalType, QueryParamsKeys, TimeSlot, VolunteerStateEngagementType } from "need4deed-sdk"; +import { ByDay, OccasionalType, QueryParamsKeys, TimeSlot, VolunteerStateEngagementType, VolunteerStateTypeType } from "need4deed-sdk"; import { CardsFilter } from "./types"; export const defaultVolunteerCardsFilter: CardsFilter = { [QueryParamsKeys.SEARCH]: "", [QueryParamsKeys.ACCOMPANYING]: false, + type: { + [VolunteerStateTypeType.ACCOMPANYING]: false, + [VolunteerStateTypeType.REGULAR]: false, + [VolunteerStateTypeType.EVENTS]: false, + [VolunteerStateTypeType.REGULAR_ACCOMPANYING]: false, + }, [QueryParamsKeys.DISTRICT]: {}, [QueryParamsKeys.LANGUAGE]: {}, [QueryParamsKeys.ENGAGEMENT]: { diff --git a/src/components/Dashboard/Volunteers/Filters/helpers.ts b/src/components/Dashboard/Volunteers/Filters/helpers.ts index 000da7df..1d982d69 100644 --- a/src/components/Dashboard/Volunteers/Filters/helpers.ts +++ b/src/components/Dashboard/Volunteers/Filters/helpers.ts @@ -12,6 +12,13 @@ export const createFilterItems = (filter: CardsFilter, setFilter: SetFilter t(`dashboard.volunteers.filters.volunteerType_options.${key}`), + ); + const districtFilters = generateNestedFilterControlItems( filter[QueryParamsKeys.DISTRICT], setFilter, @@ -35,7 +42,7 @@ export const createFilterItems = (filter: CardsFilter, setFilter: SetFilter { const filterItems = createFilterItems(filter, setFilter, t); - const { districtFilters, engagementFilters, languageFilters, availabilityFilters, accompanyingFilter } = filterItems; + const { districtFilters, engagementFilters, languageFilters, availabilityFilters, accompanyingFilter, typeFilters } = filterItems; const flatAvFilters = availabilityFilters.map((avFilter) => avFilter.items).flat(); - return [accompanyingFilter, ...districtFilters, ...engagementFilters, ...languageFilters, ...flatAvFilters].filter( + return [accompanyingFilter, ...typeFilters, ...districtFilters, ...engagementFilters, ...languageFilters, ...flatAvFilters].filter( (f) => f.checked, ); }; diff --git a/src/components/Dashboard/Volunteers/Filters/types.ts b/src/components/Dashboard/Volunteers/Filters/types.ts index 6be08f58..0c0d508a 100644 --- a/src/components/Dashboard/Volunteers/Filters/types.ts +++ b/src/components/Dashboard/Volunteers/Filters/types.ts @@ -10,6 +10,7 @@ export interface CardsFilter { [QueryParamsKeys.AVAILABILITY]: Availability; [QueryParamsKeys.DISTRICT]: SelectionMap; [QueryParamsKeys.LANGUAGE]: SelectionMap; + type: SelectionMap; } export type CardFilterKeys = keyof CardsFilter; diff --git a/src/components/Dashboard/Volunteers/helpers.ts b/src/components/Dashboard/Volunteers/helpers.ts index 0b2116ca..e13bb087 100644 --- a/src/components/Dashboard/Volunteers/helpers.ts +++ b/src/components/Dashboard/Volunteers/helpers.ts @@ -77,6 +77,11 @@ export function serializeFilters( if (filter.accompanying) params.set(QueryParamsKeys.ACCOMPANYING, "true"); else params.delete(QueryParamsKeys.ACCOMPANYING); + params.delete("type"); + Object.entries(filter.type).forEach(([key, value]) => { + if (value === true) params.append("type", key); + }); + // 2. Clear all existing 'district' params params.delete(QueryParamsKeys.DISTRICT); Object.entries(filter.district).forEach(([key, value]) => { @@ -144,6 +149,13 @@ export function deserializeVolunteerFilters(filter: CardsFilter, searchParams: R newFilter.accompanying = true; } + const queryTypes = searchParams.getAll("type"); + queryTypes.forEach((t) => { + if (newFilter.type[t] !== undefined) { + newFilter.type[t] = true; + } + }); + const queryDistricts = searchParams.getAll(QueryParamsKeys.DISTRICT); queryDistricts.forEach((d) => { // Check if the query param value is exist in the filters. if not, ignore that query param !!! From 521308ef6bb0f3fc2875f550c8ed400c595f4bd8 Mon Sep 17 00:00:00 2001 From: nadavosa Date: Mon, 11 May 2026 19:15:35 +0200 Subject: [PATCH 2/6] feat: show match status on volunteer cards Closes https://github.com/need4deed-org/fe/issues/423 Co-Authored-By: Claude Sonnet 4.6 --- .../Dashboard/Volunteers/VolunteerCard.tsx | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/components/Dashboard/Volunteers/VolunteerCard.tsx b/src/components/Dashboard/Volunteers/VolunteerCard.tsx index 31f2d209..f2916f89 100644 --- a/src/components/Dashboard/Volunteers/VolunteerCard.tsx +++ b/src/components/Dashboard/Volunteers/VolunteerCard.tsx @@ -3,6 +3,7 @@ import { ApiVolunteerGetList, VolunteerStateCommunicationType, VolunteerStateEngagementType, + VolunteerStateMatchType, VolunteerStateTypeType, } from "need4deed-sdk"; import { useRouter } from "next/navigation"; @@ -31,14 +32,10 @@ export function VolunteerCard({ volunteer, opportunityId }: Props) { const { t, i18n } = useTranslation(); const router = useRouter(); - const { id, name, languages, activities, skills, locations, availability, avatarUrl, statusEngagement, statusType } = + const { id, name, languages, activities, skills, locations, availability, avatarUrl, statusEngagement, statusType, statusCommunication, statusMatch } = getNormalizedVolunteer(volunteer); - // TODO: remove cast once SDK adds statusCommunication to ApiVolunteerGetList - const { statusCommunication } = volunteer as ApiVolunteerGetList & { - statusCommunication?: VolunteerStateCommunicationType; - }; - const showBriefedCheck = isBriefedAccompanying(statusType as VolunteerStateTypeType, statusCommunication); + const showBriefedCheck = isBriefedAccompanying(statusType as VolunteerStateTypeType, statusCommunication as VolunteerStateCommunicationType); const groupedLanguages = groupLanguagesByProficiency(languages); @@ -87,6 +84,19 @@ export function VolunteerCard({ volunteer, opportunityId }: Props) { )} + + {statusMatch && ( + + + {t(`dashboard.volunteers.matchStatus.${statusMatch}`)} + + + )} @@ -141,6 +151,13 @@ export default VolunteerCard; /* Helper maps */ +const stateMatchColorMap: Record = { + [VolunteerStateMatchType.NO_MATCHES]: "var(--color-grey-700)", + [VolunteerStateMatchType.PENDING_MATCH]: "var(--color-blue-700)", + [VolunteerStateMatchType.MATCHED]: "var(--color-green-700)", + [VolunteerStateMatchType.NEEDS_REMATCH]: "var(--color-red-700)", +}; + const stateEngagementColorMap: Record = { [VolunteerStateEngagementType.NEW]: "var(--color-red-500)", [VolunteerStateEngagementType.ACTIVE]: "var(--color-green-700)", From 390639010fe93c4dd66c40f1e24927a32c18948d Mon Sep 17 00:00:00 2001 From: nadavosa Date: Mon, 11 May 2026 19:19:57 +0200 Subject: [PATCH 3/6] feat: wire statusMatch into volunteer table view Part of https://github.com/need4deed-org/fe/issues/423 Co-Authored-By: Claude Sonnet 4.6 --- .../Dashboard/Profile/sections/VolunteerAgents/types.ts | 8 ++++++++ .../Dashboard/Volunteers/VolunteerTableList.tsx | 3 +++ src/components/Dashboard/Volunteers/VolunteerTableRow.tsx | 8 +++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts b/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts index 6b33d301..4d07e95d 100644 --- a/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts +++ b/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts @@ -2,6 +2,7 @@ import { ApiVolunteerGetList, OpportunityVolunteerStatusType, VolunteerStateEngagementType, + VolunteerStateMatchType, VolunteerStateTypeType, } from "need4deed-sdk"; @@ -20,6 +21,13 @@ export const createEngagementStatusLabelMap = (t: TFunction): Record => ({ + [VolunteerStateMatchType.NO_MATCHES]: t("dashboard.volunteers.matchStatus.vol-no-matches"), + [VolunteerStateMatchType.PENDING_MATCH]: t("dashboard.volunteers.matchStatus.vol-pending-match"), + [VolunteerStateMatchType.MATCHED]: t("dashboard.volunteers.matchStatus.vol-matched"), + [VolunteerStateMatchType.NEEDS_REMATCH]: t("dashboard.volunteers.matchStatus.vol-needs-rematch"), +}); + export const createStatusLabelMap = (t: TFunction): Record => ({ [VolunteerStateTypeType.ACCOMPANYING]: t( "dashboard.volunteerProfile.volunteerHeader.volunteerType_options.accompanying", diff --git a/src/components/Dashboard/Volunteers/VolunteerTableList.tsx b/src/components/Dashboard/Volunteers/VolunteerTableList.tsx index fd8d90b7..0f0b065b 100644 --- a/src/components/Dashboard/Volunteers/VolunteerTableList.tsx +++ b/src/components/Dashboard/Volunteers/VolunteerTableList.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import styled from "styled-components"; import { createEngagementStatusLabelMap, + createMatchStatusLabelMap, createStatusLabelMap, } from "@/components/Dashboard/Profile/sections/VolunteerAgents/types"; import { Table, TableBody, TableContainer, TableHeader, TableHeaderCell } from "@/components/core/common/Table"; @@ -34,6 +35,7 @@ export function VolunteerTableList({ const engagementLabels = useMemo(() => createEngagementStatusLabelMap(t), [t]); const typeLabels = useMemo(() => createStatusLabelMap(t), [t]); + const matchLabels = useMemo(() => createMatchStatusLabelMap(t), [t]); const goToPage = (page: number) => { if (page > 0 && page <= totalPages) setCurrentPage(page); @@ -60,6 +62,7 @@ export function VolunteerTableList({ isLast={index === volunteers.length - 1} engagementLabels={engagementLabels} typeLabels={typeLabels} + matchLabels={matchLabels} opportunityId={opportunityId} /> ))} diff --git a/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx b/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx index 268dcaf1..1a4e48ff 100644 --- a/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx +++ b/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import styled from "styled-components"; import type { createEngagementStatusLabelMap, + createMatchStatusLabelMap, createStatusLabelMap, } from "@/components/Dashboard/Profile/sections/VolunteerAgents/types"; import { TableCell, TableRow } from "@/components/core/common/Table"; @@ -18,14 +19,15 @@ interface TableRowProps { isLast: boolean; engagementLabels: ReturnType; typeLabels: ReturnType; + matchLabels: ReturnType; opportunityId?: string; } -export function VolunteerTableRow({ volunteer, isLast, engagementLabels, typeLabels, opportunityId }: TableRowProps) { +export function VolunteerTableRow({ volunteer, isLast, engagementLabels, typeLabels, matchLabels, opportunityId }: TableRowProps) { const { i18n } = useTranslation(); const router = useRouter(); - const { id, name, avatarUrl, statusEngagement, statusType, languages, locations } = volunteer; + const { id, name, avatarUrl, statusEngagement, statusType, statusMatch, languages, locations } = volunteer; const languageText = languages @@ -57,7 +59,7 @@ export function VolunteerTableRow({ volunteer, isLast, engagementLabels, typeLab {statusEngagement ? engagementLabels[statusEngagement] : "—"} - — + {statusMatch ? matchLabels[statusMatch] : "—"} {languageText} From c8696a9a0aaddca3aed058e407c2aa4b34049763 Mon Sep 17 00:00:00 2001 From: nadavosa Date: Wed, 13 May 2026 17:22:54 +0200 Subject: [PATCH 4/6] fix: add VolunteerStateMatchType.PAST to match status maps; rebase on develop SDK 0.0.83 added PAST to VolunteerStateMatchType. Both Record maps were exhaustiveness-checked and missing the new value, causing typecheck to fail. Co-Authored-By: Claude Sonnet 4.6 --- .../Dashboard/Profile/sections/VolunteerAgents/types.ts | 1 + src/components/Dashboard/Volunteers/VolunteerCard.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts b/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts index 4d07e95d..e4f01299 100644 --- a/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts +++ b/src/components/Dashboard/Profile/sections/VolunteerAgents/types.ts @@ -26,6 +26,7 @@ export const createMatchStatusLabelMap = (t: TFunction): Record => ({ diff --git a/src/components/Dashboard/Volunteers/VolunteerCard.tsx b/src/components/Dashboard/Volunteers/VolunteerCard.tsx index f2916f89..b9a6337e 100644 --- a/src/components/Dashboard/Volunteers/VolunteerCard.tsx +++ b/src/components/Dashboard/Volunteers/VolunteerCard.tsx @@ -156,6 +156,7 @@ const stateMatchColorMap: Record = { [VolunteerStateMatchType.PENDING_MATCH]: "var(--color-blue-700)", [VolunteerStateMatchType.MATCHED]: "var(--color-green-700)", [VolunteerStateMatchType.NEEDS_REMATCH]: "var(--color-red-700)", + [VolunteerStateMatchType.PAST]: "var(--color-grey-500)", }; const stateEngagementColorMap: Record = { From 23291366d55b09327abe5e1cb2174615a559ee08 Mon Sep 17 00:00:00 2001 From: nadavosa Date: Fri, 15 May 2026 15:51:20 +0200 Subject: [PATCH 5/6] fix: cast statusMatch and statusCommunication until SDK PR #99 merges These fields are not yet in the published SDK (need4deed-sdk@0.0.85). Restore the cast pattern from before to unblock typecheck. Co-Authored-By: Claude Sonnet 4.6 --- src/components/Dashboard/Volunteers/VolunteerCard.tsx | 10 ++++++++-- .../Dashboard/Volunteers/VolunteerTableRow.tsx | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/Dashboard/Volunteers/VolunteerCard.tsx b/src/components/Dashboard/Volunteers/VolunteerCard.tsx index b9a6337e..280aefbe 100644 --- a/src/components/Dashboard/Volunteers/VolunteerCard.tsx +++ b/src/components/Dashboard/Volunteers/VolunteerCard.tsx @@ -32,10 +32,16 @@ export function VolunteerCard({ volunteer, opportunityId }: Props) { const { t, i18n } = useTranslation(); const router = useRouter(); - const { id, name, languages, activities, skills, locations, availability, avatarUrl, statusEngagement, statusType, statusCommunication, statusMatch } = + const { id, name, languages, activities, skills, locations, availability, avatarUrl, statusEngagement, statusType } = getNormalizedVolunteer(volunteer); - const showBriefedCheck = isBriefedAccompanying(statusType as VolunteerStateTypeType, statusCommunication as VolunteerStateCommunicationType); + // Cast until SDK PR #99 adds statusCommunication and statusMatch to ApiVolunteerGetList + const { statusCommunication, statusMatch } = volunteer as ApiVolunteerGetList & { + statusCommunication?: VolunteerStateCommunicationType; + statusMatch?: VolunteerStateMatchType; + }; + + const showBriefedCheck = isBriefedAccompanying(statusType as VolunteerStateTypeType, statusCommunication); const groupedLanguages = groupLanguagesByProficiency(languages); diff --git a/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx b/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx index 1a4e48ff..f9a82e65 100644 --- a/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx +++ b/src/components/Dashboard/Volunteers/VolunteerTableRow.tsx @@ -1,6 +1,6 @@ "use client"; -import { ApiVolunteerGetList } from "need4deed-sdk"; +import { ApiVolunteerGetList, VolunteerStateMatchType } from "need4deed-sdk"; import { useRouter } from "next/navigation"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; @@ -27,7 +27,9 @@ export function VolunteerTableRow({ volunteer, isLast, engagementLabels, typeLab const { i18n } = useTranslation(); const router = useRouter(); - const { id, name, avatarUrl, statusEngagement, statusType, statusMatch, languages, locations } = volunteer; + const { id, name, avatarUrl, statusEngagement, statusType, languages, locations } = volunteer; + // Cast until SDK PR #99 adds statusMatch to ApiVolunteerGetList + const { statusMatch } = volunteer as ApiVolunteerGetList & { statusMatch?: VolunteerStateMatchType }; const languageText = languages From e169cb8c4c897137c32722a3898ee92af4f4a8e2 Mon Sep 17 00:00:00 2001 From: Nadav Nir Date: Wed, 20 May 2026 12:35:44 +0200 Subject: [PATCH 6/6] fix: add missing volunteer type filter and match status translation keys Adds to dashboard.volunteers.filters: volunteerType_title, volunteerType_options Adds to dashboard.volunteers: matchStatus (vol-* keys) Both EN and DE locales updated. Co-Authored-By: Claude Sonnet 4.6 --- public/locales/de/translations.json | 14 ++++++++++++++ public/locales/en/translations.json | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/public/locales/de/translations.json b/public/locales/de/translations.json index a2c459d2..b0d54b76 100644 --- a/public/locales/de/translations.json +++ b/public/locales/de/translations.json @@ -400,6 +400,13 @@ "weekdays": "Wochentage", "weekends": "Wochenenden" } + }, + "volunteerType_title": "Freiwilligen-Typ", + "volunteerType_options": { + "accompanying": "Begleitend", + "regular": "Regelmäßig", + "events": "Veranstaltungen", + "regular-accompanying": "Regelmäßig + Begleitend" } }, "volunteers": "Freiwillige", @@ -414,6 +421,13 @@ "advanced": "Fortgeschritten", "fluent": "Fließend", "native": "Muttersprache" + }, + "matchStatus": { + "vol-no-matches": "Keine Zuweisungen", + "vol-pending-match": "Zuordnung ausstehend", + "vol-matched": "Zugeordnet", + "vol-needs-rematch": "Erneute Zuordnung erforderlich", + "vol-past": "Vergangen" } }, "home": { diff --git a/public/locales/en/translations.json b/public/locales/en/translations.json index 4bc9a65f..631abc7f 100644 --- a/public/locales/en/translations.json +++ b/public/locales/en/translations.json @@ -400,6 +400,13 @@ "weekdays": "Weekdays", "weekends": "Weekends" } + }, + "volunteerType_title": "Volunteer type", + "volunteerType_options": { + "accompanying": "Accompanying", + "regular": "Regular", + "events": "Events", + "regular-accompanying": "Regular + Accompanying" } }, "volunteers": "Volunteers", @@ -414,6 +421,13 @@ "advanced": "Advanced", "fluent": "Fluent", "native": "Native" + }, + "matchStatus": { + "vol-no-matches": "No matches", + "vol-pending-match": "Pending match", + "vol-matched": "Matched", + "vol-needs-rematch": "Needs rematch", + "vol-past": "Past" } }, "home": {