import { useMemo, useState } from 'react'; import { ActionIcon, Alert, Badge, Button, Card, Checkbox, Group, Loader, Modal, ScrollArea, SegmentedControl, Select, Stack, Table, Text, TextInput, Tooltip, } from '@mantine/core'; import { IconAdjustments, IconAlertCircle, IconCheck, IconDownload, IconInfoCircle, IconSearch, } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; import { ModalFooter } from '@ema-platform/ui'; import { useCreateDocumentRequirementMutation, useGetLicenseTypesQuery, useGetPersonalDocumentsQuery, useLocalized, type ApplicationKind, type DocumentRequirement, type LicenseType, type PersonalDocumentGroup, } from '@ema-platform/api'; import { useRequirementActions } from '../hooks/useRequirementActions'; import type { DraftRequirement } from './DocumentRequirementEditorDrawer'; const MIME_LABELS: Record = { 'application/pdf': 'PDF', 'application/msword': 'DOC', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX', 'application/vnd.ms-excel': 'XLS', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX', }; function shortMime(mime: string): string { return MIME_LABELS[mime] ?? mime.split('/')[1]?.toUpperCase() ?? mime; } interface ImportPersonalDocumentsModalProps { opened: boolean; onClose: () => void; licenseType: LicenseType; applicationKind: ApplicationKind; existingKeys: string[]; currentMaxSortOrder: number; onCustomizeAndAdd: (draft: Partial) => void; } export function ImportPersonalDocumentsModal({ opened, onClose, licenseType, applicationKind, existingKeys, currentMaxSortOrder, onCustomizeAndAdd, }: ImportPersonalDocumentsModalProps) { const { t, i18n } = useTranslation(); const localized = useLocalized(); const run = useRequirementActions(); const [search, setSearch] = useState(''); const [scopeFilter, setScopeFilter] = useState<'all' | 'scoped'>('all'); const [importMode, setImportMode] = useState('ALWAYS'); const [selectedKeys, setSelectedKeys] = useState([]); const { data: licenseTypes } = useGetLicenseTypesQuery(); const { data: personalDocsData, isLoading, isError, refetch } = useGetPersonalDocumentsQuery({ take: 100, locale: i18n.language === 'am' ? 'am' : 'en', }); const [createRequirement, { isLoading: isCreating }] = useCreateDocumentRequirementMutation(); const groups = useMemo(() => { return (personalDocsData?.items ?? []).map((group) => { const scopeIds = group.rows .map((r) => r.licenseTypeId) .filter((id): id is string => id !== null); const isGlobal = scopeIds.length === 0; const appliesToCurrentType = isGlobal || scopeIds.includes(licenseType.id); const isAlreadyAdded = existingKeys.includes(group.key); return { ...group, scopeIds, isGlobal, appliesToCurrentType, isAlreadyAdded, mainRow: group.rows[0], }; }); }, [personalDocsData, licenseType.id, existingKeys]); const filteredGroups = useMemo(() => { const q = search.trim().toLowerCase(); return groups.filter((item) => { if (scopeFilter === 'scoped' && !item.appliesToCurrentType) { return false; } if (!q) return true; const nameEn = item.mainRow.name.en?.toLowerCase() ?? ''; const nameAm = item.mainRow.name.am?.toLowerCase() ?? ''; const key = item.key.toLowerCase(); return nameEn.includes(q) || nameAm.includes(q) || key.includes(q); }); }, [groups, search, scopeFilter]); const selectableKeys = useMemo( () => filteredGroups.filter((g) => !g.isAlreadyAdded).map((g) => g.key), [filteredGroups], ); const allSelected = selectableKeys.length > 0 && selectableKeys.every((k) => selectedKeys.includes(k)); const someSelected = selectableKeys.some((k) => selectedKeys.includes(k)) && !allSelected; function toggleSelectAll() { if (allSelected) { setSelectedKeys((prev) => prev.filter((k) => !selectableKeys.includes(k))); } else { setSelectedKeys((prev) => Array.from(new Set([...prev, ...selectableKeys]))); } } function toggleSelectOne(key: string) { setSelectedKeys((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key], ); } async function handleBatchImport() { const itemsToImport = groups.filter( (g) => selectedKeys.includes(g.key) && !g.isAlreadyAdded, ); if (itemsToImport.length === 0) return; const ok = await run( () => Promise.all( itemsToImport.map((item, index) => createRequirement({ key: item.key, name: item.mainRow.name, description: item.mainRow.description, applicationKind, mode: importMode, allowedMimeTypes: item.mainRow.allowedMimeTypes ?? ['application/pdf', 'image/jpeg', 'image/png'], maxSizeMb: item.mainRow.maxSizeMb ?? 5, requiresValidityDates: item.mainRow.requiresValidityDates ?? false, allowMultiple: (item.mainRow.maxFiles ?? 1) !== 1, isPersonal: false, maxFiles: item.mainRow.maxFiles ?? null, sortOrder: currentMaxSortOrder + index + 1, licenseTypeId: licenseType.id, }).unwrap(), ), ), t('certReq.doc.importSuccess', 'Imported {{count}} personal document(s)', { count: itemsToImport.length, }), ); if (ok) { setSelectedKeys([]); onClose(); } } function handleCustomizeRow(item: (typeof groups)[number]) { onCustomizeAndAdd({ key: item.key, name: { ...item.mainRow.name }, description: item.mainRow.description ? { ...item.mainRow.description } : undefined, applicationKind, mode: importMode, allowedMimeTypes: [...item.mainRow.allowedMimeTypes], maxSizeMb: item.mainRow.maxSizeMb, maxFiles: item.mainRow.maxFiles, requiresValidityDates: item.mainRow.requiresValidityDates, allowMultiple: (item.mainRow.maxFiles ?? 1) !== 1, isPersonal: false, sortOrder: currentMaxSortOrder + 1, }); onClose(); } return ( {t('certReq.doc.importPersonal', 'Import from personal documents')} {localized(licenseType.name)} · {applicationKind} } size="lg" padding="md" > } > {t( 'certReq.doc.importedHelper', 'Imported from personal documents. The key matches the vault so uploaded files will link automatically.', )} } value={search} onChange={(e) => setSearch(e.currentTarget.value)} size="xs" style={{ flex: 1, minWidth: 200 }} /> setScopeFilter(v as 'all' | 'scoped')} data={[ { value: 'all', label: t('certReq.personal.filterAny', 'All personal docs') }, { value: 'scoped', label: t('certReq.doc.scopeSelected', 'Relevant to licence'), }, ]} />