/* documents.jsx — Документы: паспорт/права/СНИЛС и др., свои и чужие, с авто-распознаванием по фото. */
const DOC_TYPES = [
{ v: "passport", label: "Паспорт" },
{ v: "license", label: "Права" },
{ v: "snils", label: "СНИЛС" },
{ v: "inn", label: "ИНН" },
{ v: "other", label: "Другое" },
];
const docTypeLabel = (t) => (DOC_TYPES.find((x) => x.v === t) || { label: "Документ" }).label;
function DocumentsScreen() {
const [items, setItems] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [q, setQ] = React.useState("");
const [openId, setOpenId] = React.useState(null);
const [scanning, setScanning] = React.useState(false);
const [err, setErr] = React.useState("");
const fileRef = React.useRef(null);
const load = React.useCallback(async () => {
setLoading(true);
setItems(await api.getDocuments({ q: q || undefined }));
setLoading(false);
}, [q]);
React.useEffect(() => { const t = setTimeout(load, 200); return () => clearTimeout(t); }, [q]);
const onFile = async (e) => {
const f = (e.target.files || [])[0];
e.target.value = "";
if (!f) return;
setErr(""); setScanning(true);
try {
const doc = await api.scanDocument(f);
await load();
setOpenId(doc.id); // открываем для проверки распознанного
} catch (er) {
setErr("Не удалось распознать. Попробуй фото чётче или добавь вручную.");
} finally { setScanning(false); }
};
const addBlank = async () => {
const doc = await api.createDocument({ doc_type: "passport", title: "Паспорт" });
await load();
setOpenId(doc.id);
};
return (
Загрузи фото документа — данные распознаются автоматически. Хранилище личное; держи доступ к аккаунту в секрете.
{err &&
{err}
}
setQ(e.target.value)} placeholder="Поиск по владельцу, типу, заметке…"
className="w-full h-11 rounded-xl border border-[var(--border)] bg-[var(--surface)] pl-10 pr-3 text-sm text-[var(--text)] outline-none focus:border-[var(--accent)] transition placeholder:text-[var(--muted)]" />
{loading ? (
) : items.length === 0 ? (
) : (
{items.map((d, i) => setOpenId(d.id)} />)}
)}
setOpenId(null)} onChanged={load} />
);
}
function DocumentRow({ d, index = 0, onOpen }) {
const photo = (d.photos || [])[0];
return (
);
}
function DocumentModal({ docId, open, onClose, onChanged }) {
const [data, setData] = React.useState(null);
const [form, setForm] = React.useState({});
const [rows, setRows] = React.useState([]); // [{k,v}]
const [busy, setBusy] = React.useState(false);
const [confirmDel, setConfirmDel] = React.useState(false);
const photoRef = React.useRef(null);
const fill = (d) => {
setData(d);
setForm({ owner_name: d.owner_name || "", doc_type: d.doc_type || "other", title: d.title || "", note: d.note || "" });
setRows(Object.entries(d.fields || {}).map(([k, v]) => ({ k, v })));
};
React.useEffect(() => {
if (open && docId) { setData(null); setConfirmDel(false); api.getDocument(docId).then(fill); }
}, [open, docId]);
const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
const setRow = (i, key, val) => setRows((r) => r.map((x, j) => (j === i ? { ...x, [key]: val } : x)));
const addRow = () => setRows((r) => [...r, { k: "", v: "" }]);
const delRow = (i) => setRows((r) => r.filter((_, j) => j !== i));
const save = async () => {
setBusy(true);
const fields = {};
rows.forEach((x) => { if (x.k.trim()) fields[x.k.trim()] = x.v; });
await api.updateDocument(docId, { ...form, fields });
setBusy(false);
onChanged && onChanged();
onClose();
};
const del = async () => { setBusy(true); await api.deleteDocument(docId); setBusy(false); onChanged && onChanged(); onClose(); };
const onPhoto = async (e) => {
const f = (e.target.files || [])[0]; e.target.value = "";
if (!f) return;
setBusy(true);
try { const d = await api.addDocumentPhoto(docId, f); fill(d); } catch (er) {} finally { setBusy(false); }
};
return (
{form.title || "Документ"}
{!data ? (
) : (
<>
{!confirmDel ? (
) : (
)}
>
)}
);
}
Object.assign(window, { DocumentsScreen });