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
2 changes: 2 additions & 0 deletions public/locales/de/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,8 @@
"typeOfDocument": "Dokumenttyp",
"status": "Status",
"uploadedOn": "Hochgeladen am",
"received": "Erhalten",
"receivedOn": "Erhalten am",
"actions": "Aktionen",
"uploaded": "Hochgeladen",
"missing": "Fehlend",
Expand Down
2 changes: 2 additions & 0 deletions public/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@
"typeOfDocument": "Type of document",
"status": "Status",
"uploadedOn": "Uploaded on",
"received": "Received",
"receivedOn": "Received on",
"actions": "Actions",
"uploaded": "Uploaded",
"missing": "Missing",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { EmptyPlaceholder } from "@/components/core/common/EmptyPlaceholder";
import { DownloadSimple, Eye, Trash, UploadSimple } from "@phosphor-icons/react";
import { useTranslation } from "react-i18next";
import { ActionButtonWithTooltip } from "./ActionButtonWithTooltip";
import { ActionCell, Cell, StatusBadge, TableRow } from "./styles";
import { ActionCell, Cell, ReceivedCell, ReceivedCheckbox, StatusBadge, TableRow } from "./styles";
import { DocumentRow } from "./utils";

type Props = {
Expand All @@ -12,6 +12,7 @@ type Props = {
onPreview: () => void;
onDownload: () => void;
onDelete: () => void;
onToggleReceived: () => void;
};

export function DocumentTableRow({
Expand All @@ -21,28 +22,42 @@ export function DocumentTableRow({
onPreview,
onDownload,
onDelete,
onToggleReceived,
}: Props) {
const { t } = useTranslation();
const { nameKey, isUploaded, document } = documentRow;
const { nameKey, isUploaded, isReceived, receivedAt, document } = documentRow;

return (
<TableRow $isLast={isLast}>
<Cell>{t(`dashboard.documentSection.documentNames.${nameKey}`)}</Cell>
<ReceivedCell>
<ReceivedCheckbox
checked={isReceived}
onChange={onToggleReceived}
aria-label={t("dashboard.documentSection.received")}
/>
</ReceivedCell>
<Cell $width="180px" $align="center">
<StatusBadge $status={isUploaded ? "uploaded" : "missing"}>
{isUploaded
? t("dashboard.documentSection.uploaded")
: t("dashboard.documentSection.missing")}
<StatusBadge $status={isUploaded || isReceived ? "uploaded" : "missing"}>
{isUploaded || isReceived ? t("dashboard.documentSection.uploaded") : t("dashboard.documentSection.missing")}
</StatusBadge>
</Cell>
<Cell $width="152px" $noWrap>
{document?.createdAt
? new Date(document.createdAt).toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})
: <EmptyPlaceholder />}
{document?.createdAt ? (
new Date(document.createdAt).toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})
) : isReceived && receivedAt ? (
receivedAt.toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
})
) : (
<EmptyPlaceholder />
)}
</Cell>
<ActionCell>
<ActionButtonWithTooltip
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";
import { useVolunteerDocuments } from "@/hooks/useVolunteerDocuments";
import { ApiVolunteerGet } from "need4deed-sdk";
import { ApiVolunteerGet, DocumentType } from "need4deed-sdk";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "react-toastify";
Expand All @@ -11,7 +11,7 @@ import { DocumentTableRow } from "./DocumentTableRow";
import { ACTION_COLUMN_WIDTH, DocumentTableContainer, HeaderCell, Table, TableHeader } from "./styles";
import { UploadDocumentDialog } from "./UploadDocumentDialog";
import { useDialogState } from "./useDialogState";
import { useDeleteDocument, useUploadDocument } from "./useDocumentOperations";
import { useDeleteDocument, useMarkDocumentReceived, useUploadDocument } from "./useDocumentOperations";
import { DocumentRow, enrichDocuments, extractDocumentUrl } from "./utils";

type Props = {
Expand All @@ -37,11 +37,16 @@ export function VolunteerProfileDocument({ volunteer }: Props) {

const documentRows = useMemo(() => (fetchedDocuments ? enrichDocuments(fetchedDocuments) : []), [fetchedDocuments]);

const handleToggleReceived = (type: DocumentType, currentIsReceived: boolean) => {
receivedMutation.mutate({ volunteerId: volunteer.id, documentType: type, received: !currentIsReceived });
};

const uploadMutation = useUploadDocument(volunteer.id, () => closeDialog("upload"));
const deleteMutation = useDeleteDocument(volunteer.id, () => {
closeDialog("delete");
closeDialog("preview");
});
const receivedMutation = useMarkDocumentReceived(volunteer.id);

const handleConfirmDelete = () => {
if (deleteDialogDocument) {
Expand Down Expand Up @@ -103,6 +108,9 @@ export function VolunteerProfileDocument({ volunteer }: Props) {
<Table>
<TableHeader>
<HeaderCell>{t("dashboard.documentSection.typeOfDocument")}</HeaderCell>
<HeaderCell $width="120px" $noWrap>
{t("dashboard.documentSection.received")}
</HeaderCell>
<HeaderCell $width="180px">{t("dashboard.documentSection.status")}</HeaderCell>
<HeaderCell $width="152px" $noWrap>
{t("dashboard.documentSection.uploadedOn")}
Expand All @@ -122,6 +130,7 @@ export function VolunteerProfileDocument({ volunteer }: Props) {
onPreview={() => handlePreview(row)}
onDownload={() => handleDownload(row)}
onDelete={() => openDialog("delete", row)}
onToggleReceived={() => handleToggleReceived(row.type, row.isReceived)}
/>
))}
</Table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,19 @@ export const ActionCell = styled(Cell).attrs<{ $width?: string; $align?: string
}))`
padding: var(--document-section-action-cell-padding);
`;

export const ReceivedCell = styled(Cell)`
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
width: 120px;
flex: none;
`;

export const ReceivedCheckbox = styled.input.attrs({ type: "checkbox" })`
width: 18px;
height: 18px;
cursor: pointer;
accent-color: var(--color-aubergine);
`;
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ const deleteDocumentApi = async (volunteerId: number, documentType: DocumentType
await axios.delete(`${apiPathVolunteer}/${volunteerId}/doc/${documentType}`);
};

type MarkReceivedPayload = {
volunteerId: number;
documentType: DocumentType;
received: boolean;
};

const markDocumentReceivedApi = async (
volunteerId: number,
documentType: DocumentType,
received: boolean,
): Promise<void> => {
await axios.patch(`${apiPathVolunteer}/${volunteerId}/doc/${documentType}`, { received });
};

export const useUploadDocument = (volunteerId: number, onSuccess?: () => void) => {
const { t } = useTranslation();

Expand All @@ -74,3 +88,10 @@ export const useDeleteDocument = (volunteerId: number, onSuccess?: () => void) =
onSuccessCallback: onSuccess,
});
};

export const useMarkDocumentReceived = (volunteerId: number) => {
return useMutationQuery<MarkReceivedPayload, void>({
mutationFn: ({ documentType, received }) => markDocumentReceivedApi(volunteerId, documentType, received),
queryKeyToInvalidate: ["volunteerDocuments", volunteerId.toString()],
});
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { ApiDocumentGet, DocumentType } from "need4deed-sdk";

export type EnrichedDocument = ApiDocumentGet & {
// TODO: remove cast once SDK is updated to include received and receivedOn (added by BE PR #417)
export type ApiDocumentGetWithReceived = ApiDocumentGet & {
received?: boolean;
receivedOn?: string;
};

export type EnrichedDocument = ApiDocumentGetWithReceived & {
nameKey: string;
isUploaded: boolean;
};
Expand All @@ -9,7 +15,9 @@ export type DocumentRow = {
type: DocumentType;
nameKey: string;
isUploaded: boolean;
document?: ApiDocumentGet;
document?: ApiDocumentGetWithReceived;
isReceived: boolean;
receivedAt: Date | null;
};

const DOCUMENT_NAME_KEYS: Record<DocumentType, string> = {
Expand Down Expand Up @@ -40,18 +48,19 @@ export const extractDocumentUrl = (url: string): string | null => {
}
};

export const enrichDocuments = (
fetchedDocuments: ApiDocumentGet[]
): DocumentRow[] => {
export const enrichDocuments = (fetchedDocuments: ApiDocumentGet[]): DocumentRow[] => {
const allTypes = Object.keys(DOCUMENT_NAME_KEYS) as DocumentType[];

return allTypes.map((type) => {
const document = fetchedDocuments.find((doc) => doc.type === type);
// TODO: remove cast once SDK is updated to include received and receivedOn (added by BE PR #417)
const document = fetchedDocuments.find((doc) => doc.type === type) as ApiDocumentGetWithReceived | undefined;
return {
type,
nameKey: DOCUMENT_NAME_KEYS[type],
isUploaded: !!document,
document,
isReceived: document?.received ?? false,
receivedAt: document?.receivedOn ? new Date(document.receivedOn) : null,
};
});
};
Loading