Files
papercrate/frontend/src/app/useManagementModals.tsx
T
2025-11-13 02:37:16 +01:00

237 lines
6.4 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ReactNode } from 'react';
import TagsPanel from '../tags/TagsPanel';
import CorrespondentsPanel, { CorrespondentsPanelProps } from '../correspondents/CorrespondentsPanel';
import PanelHeader from '../ui/PanelHeader';
const TAGS_MODAL = 'tags';
const CORRESPONDENTS_MODAL = 'correspondents';
interface TagRecord {
id?: string | number;
label?: string;
[key: string]: unknown;
}
interface CorrespondentRecord {
id?: string | number;
name?: string;
[key: string]: unknown;
}
interface UseManagementModalsArgs {
locationPathname?: string;
tags?: TagRecord[];
refreshTags?: () => void | Promise<void>;
onTagCreate?: (...args: any[]) => void | Promise<void>;
onTagUpdate?: (...args: any[]) => void | Promise<void>;
onTagDelete?: (...args: any[]) => void | Promise<void>;
correspondents?: CorrespondentRecord[];
refreshCorrespondents?: () => void | Promise<void>;
onCorrespondentCreate?: (...args: any[]) => void | Promise<void>;
onCorrespondentUpdate?: (...args: any[]) => void | Promise<void>;
onCorrespondentDelete?: (...args: any[]) => void | Promise<void>;
setStatusMessage?: (message: string, variant?: string) => void;
}
interface UseManagementModalsResult {
managementModals: ReactNode;
openTagsModal: () => void;
openCorrespondentsModal: () => void;
closeActiveModal: () => void;
activeModal: string | null;
}
export const useManagementModals = ({
locationPathname,
tags = [],
refreshTags,
onTagCreate,
onTagUpdate,
onTagDelete,
correspondents = [],
refreshCorrespondents,
onCorrespondentCreate,
onCorrespondentUpdate,
onCorrespondentDelete,
setStatusMessage,
}: UseManagementModalsArgs): UseManagementModalsResult => {
const [activeModal, setActiveModal] = useState<string | null>(null);
const openTagsModal = useCallback(() => setActiveModal(TAGS_MODAL), []);
const openCorrespondentsModal = useCallback(
() => setActiveModal(CORRESPONDENTS_MODAL),
[],
);
const closeActiveModal = useCallback(() => setActiveModal(null), []);
useEffect(() => {
setActiveModal(null);
}, [locationPathname]);
useEffect(() => {
if (!activeModal) {
return undefined;
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault();
setActiveModal(null);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [activeModal]);
const tagModal = useMemo(() => {
if (activeModal !== TAGS_MODAL) {
return null;
}
return (
<div
className="modal-backdrop"
role="presentation"
onClick={closeActiveModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="tags-modal-title"
onClick={(event) => event.stopPropagation()}
>
<PanelHeader
className="panel-modal__header"
title="Manage Tags"
titleTag="h3"
titleProps={{ id: 'tags-modal-title' }}
actions={(
<button type="button" className="secondary" onClick={closeActiveModal}>
Close
</button>
)}
/>
<div className="panel-modal__body">
<TagsPanel
tags={tags}
onRefresh={refreshTags}
onCreateTag={onTagCreate}
onUpdateTag={onTagUpdate}
onDeleteTag={onTagDelete}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
);
}, [
activeModal,
closeActiveModal,
onTagCreate,
onTagDelete,
onTagUpdate,
refreshTags,
setStatusMessage,
tags,
]);
const handleCorrespondentCreateSafe = useCallback<CorrespondentsPanelProps['onCreate']>(
async (payload) => {
if (typeof onCorrespondentCreate !== 'function') {
return undefined;
}
return onCorrespondentCreate(payload) ?? undefined;
},
[onCorrespondentCreate],
);
const handleCorrespondentUpdateSafe = useCallback<CorrespondentsPanelProps['onUpdate']>(
async (id, payload) => {
if (typeof onCorrespondentUpdate !== 'function') {
return;
}
await onCorrespondentUpdate(id, payload);
},
[onCorrespondentUpdate],
);
const handleCorrespondentDeleteSafe = useCallback<CorrespondentsPanelProps['onDelete']>(
async (id) => {
if (typeof onCorrespondentDelete !== 'function') {
return;
}
await onCorrespondentDelete(id);
},
[onCorrespondentDelete],
);
const correspondentsModal = useMemo(() => {
if (activeModal !== CORRESPONDENTS_MODAL) {
return null;
}
return (
<div
className="modal-backdrop"
role="presentation"
onClick={closeActiveModal}
>
<div
className="modal modal--panel"
role="dialog"
aria-modal="true"
aria-labelledby="correspondents-modal-title"
onClick={(event) => event.stopPropagation()}
>
<PanelHeader
className="panel-modal__header"
title="Manage Correspondents"
titleTag="h3"
titleProps={{ id: 'correspondents-modal-title' }}
actions={(
<button type="button" className="secondary" onClick={closeActiveModal}>
Close
</button>
)}
/>
<div className="panel-modal__body">
<CorrespondentsPanel
correspondents={correspondents}
onRefresh={refreshCorrespondents}
onCreate={handleCorrespondentCreateSafe}
onUpdate={handleCorrespondentUpdateSafe}
onDelete={handleCorrespondentDeleteSafe}
onNotify={setStatusMessage}
/>
</div>
</div>
</div>
);
}, [
activeModal,
closeActiveModal,
correspondents,
handleCorrespondentCreateSafe,
handleCorrespondentDeleteSafe,
handleCorrespondentUpdateSafe,
refreshCorrespondents,
setStatusMessage,
]);
const managementModals = (
<>
{tagModal}
{correspondentsModal}
</>
);
return {
managementModals,
openTagsModal,
openCorrespondentsModal,
closeActiveModal,
activeModal,
};
};
export default useManagementModals;