import { DEFAULT_FORM_FIELD_TYPES, INVOICE_STATUS_LABELS, LS_TOKEN, OPERATOR_LABELS, ROLE_LABELS, STATUS_LABELS, STATUS_KIND_LABELS, TABLE_KEY_ALIASES, TABLE_MUTATION_CONFIG, TABLE_SERVER_CONFIG, TABLE_UNALIASES, PAGE_SIZE, } from "./admin/shared/constants.js"; import { createTableState } from "./admin/shared/state.js"; import { KanbanBoard } from "./admin/features/kanban/KanbanBoard.jsx"; import { ConfigSection } from "./admin/features/config/ConfigSection.jsx"; import { DashboardSection } from "./admin/features/dashboard/DashboardSection.jsx"; import { InvoicesSection } from "./admin/features/invoices/InvoicesSection.jsx"; import { RequestsSection } from "./admin/features/requests/RequestsSection.jsx"; import { QuotesSection } from "./admin/features/quotes/QuotesSection.jsx"; import { ServiceRequestsSection } from "./admin/features/service-requests/ServiceRequestsSection.jsx"; import { RequestWorkspace } from "./admin/features/requests/RequestWorkspace.jsx"; import { AvailableTablesSection } from "./admin/features/tables/AvailableTablesSection.jsx"; import { useAdminApi } from "./admin/hooks/useAdminApi.js"; import { useAdminCatalogLoaders } from "./admin/hooks/useAdminCatalogLoaders.js"; import { useKanban } from "./admin/hooks/useKanban.js"; import { useRequestWorkspace } from "./admin/hooks/useRequestWorkspace.js"; import { useTableActions } from "./admin/hooks/useTableActions.js"; import { useTableFilterActions } from "./admin/hooks/useTableFilterActions.js"; import { useTablesState } from "./admin/hooks/useTablesState.js"; import { avatarColor, boolFilterLabel, buildUniversalQuery, canAccessSection, decodeJwtPayload, detectAttachmentPreviewKind, fallbackStatusGroup, fmtAmount, fmtBytes, fmtDateOnly, fmtKanbanDate, fmtTimeOnly, getOperatorsForType, humanizeKey, localizeMeta, localizeRequestDetails, metaKindToFilterType, metaKindToRecordType, normalizeReferenceMeta, normalizeStringList, resolveAdminObjectSrc, resolveAdminRoute, resolveAvatarSrc, resolveDeadlineTone, roleLabel, sortByName, statusLabel, translateApiError, userInitials, } from "./admin/shared/utils.js"; (function () { const { useCallback, useEffect, useMemo, useRef, useState } = React; const LEGACY_HIDDEN_DICTIONARY_TABLES = new Set(["formFields", "topicRequiredFields", "statusTransitions"]); const NEW_REQUEST_CLIENT_OPTION = "__new_client__"; function StatusLine({ status }) { return

{status?.message || ""}

; } function Section({ active, children, id }) { return (
{children}
); } function DataTable({ headers, rows, emptyColspan, renderRow, onSort, sortClause }) { return (
{headers.map((header) => { const h = typeof header === "string" ? { key: header, label: header } : header; const sortable = Boolean(h.sortable && h.field && onSort); const active = Boolean(sortable && sortClause && sortClause.field === h.field); const direction = active ? sortClause.dir : ""; return ( ); })} {rows.length ? ( rows.map((row, index) => renderRow(row, index)) ) : ( )}
onSort(h.field) : undefined} title={sortable ? "Нажмите для сортировки" : undefined} > {h.label} {sortable ? {direction === "desc" ? "↓" : "↑"} : null}
Нет данных
); } function TablePager({ tableState, onPrev, onNext, onLoadAll }) { return (
{tableState.showAll ? "Всего: " + tableState.total + " • показаны все записи" : "Всего: " + tableState.total + " • смещение: " + tableState.offset}
); } function FilterToolbar({ filters, onOpen, onRemove, onEdit, getChipLabel }) { return (
{filters.length ? ( filters.map((filter, index) => (
onEdit(index)} role="button" tabIndex={0} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); onEdit(index); } }} title="Редактировать фильтр" > {getChipLabel(filter)}
)) ) : ( Фильтры не заданы )}
); } function Overlay({ open, onClose, children, id }) { return (
{children}
); } function IconButton({ icon, tooltip, onClick, tone }) { const handleClick = (event) => { event.preventDefault(); event.stopPropagation(); if (event.nativeEvent && typeof event.nativeEvent.stopImmediatePropagation === "function") { event.nativeEvent.stopImmediatePropagation(); } if (typeof onClick === "function") onClick(event); }; const handleAuxClick = (event) => { event.preventDefault(); event.stopPropagation(); if (event.nativeEvent && typeof event.nativeEvent.stopImmediatePropagation === "function") { event.nativeEvent.stopImmediatePropagation(); } }; return ( ); } function UserAvatar({ name, email, avatarUrl, accessToken, size = 32 }) { const [broken, setBroken] = useState(false); useEffect(() => setBroken(false), [avatarUrl]); const initials = userInitials(name, email); const bg = avatarColor(name || email || initials); const src = resolveAvatarSrc(avatarUrl, accessToken); const canShowImage = Boolean(src && !broken); return ( {canShowImage ? ( {name setBroken(true)} /> ) : ( {initials} )} ); } function LoginScreen({ onSubmit, status }) { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [totpCode, setTotpCode] = useState(""); const submit = (event) => { event.preventDefault(); onSubmit(email, password, totpCode); }; return (

Вход в админ-панель

Используйте учетную запись администратора или юриста.

setEmail(event.target.value)} />
setPassword(event.target.value)} />
setTotpCode(event.target.value)} />
); } function FilterModal({ open, tableLabel, fields, draft, status, onClose, onFieldChange, onOpChange, onValueChange, onSubmit, onClear, getOperators, getFieldOptions, }) { if (!open) return null; const selectedField = fields.find((field) => field.field === draft.field) || fields[0] || null; const operators = getOperators(selectedField?.type || "text"); const options = selectedField ? getFieldOptions(selectedField) : []; return ( event.target.id === "filter-overlay" && onClose()}>
event.stopPropagation()}>

Фильтр таблицы

{tableLabel ? (draft.editIndex !== null ? "Редактирование фильтра • " : "Новый фильтр • ") + "Таблица: " + tableLabel : "Выберите поле, оператор и значение."}

{!selectedField || selectedField.type === "text" ? ( ) : selectedField.type === "number" ? ( ) : selectedField.type === "date" ? ( ) : selectedField.type === "boolean" ? ( ) : selectedField.type === "reference" || selectedField.type === "enum" ? ( ) : ( )}
); } function ReassignModal({ open, status, options, value, onChange, onClose, onSubmit, trackNumber }) { if (!open) return null; return ( event.target.id === "reassign-overlay" && onClose()}>
event.stopPropagation()}>

Переназначение заявки

{trackNumber ? "Заявка: " + trackNumber : "Выберите нового юриста"}

); } function KanbanSortModal({ open, value, status, onChange, onClose, onSubmit }) { if (!open) return null; return ( event.target.id === "kanban-sort-overlay" && onClose()}>
event.stopPropagation()}>

Сортировка канбана

Выберите способ сортировки карточек.

); } function AttachmentPreviewModal({ open, title, url, fileName, mimeType, onClose }) { const [resolvedUrl, setResolvedUrl] = useState(""); const [resolvedText, setResolvedText] = useState(""); const [resolvedKind, setResolvedKind] = useState(""); const [hint, setHint] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const decodeTextPreview = (arrayBuffer) => { const bytes = new Uint8Array(arrayBuffer || new ArrayBuffer(0)); const sampleLength = Math.min(bytes.length, 4096); let suspicious = 0; for (let i = 0; i < sampleLength; i += 1) { const byte = bytes[i]; if (byte === 0) suspicious += 4; else if (byte < 9 || (byte > 13 && byte < 32)) suspicious += 1; } if (sampleLength && suspicious / sampleLength > 0.08) return null; const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes).replace(/\u0000/g, ""); const normalized = text.length > 200000 ? text.slice(0, 200000) + "\n\n[Текст обрезан для предпросмотра]" : text; return normalized; }; useEffect(() => { if (!open || !url) { setResolvedUrl(""); setResolvedText(""); setResolvedKind(""); setHint(""); setLoading(false); setError(""); return; } const kind = detectAttachmentPreviewKind(fileName, mimeType); setResolvedKind(kind); setResolvedText(""); setHint(""); if (kind === "none") { setResolvedUrl(""); setLoading(false); setError(""); return; } let cancelled = false; let objectUrl = ""; setLoading(true); setError(""); setResolvedUrl(""); (async () => { try { const response = await fetch(url, { credentials: "same-origin" }); if (!response.ok) throw new Error("Не удалось загрузить файл для предпросмотра"); const buffer = await response.arrayBuffer(); if (cancelled) return; if (kind === "pdf") { const header = new Uint8Array(buffer.slice(0, 5)); const isPdf = header.length >= 5 && header[0] === 0x25 && header[1] === 0x50 && header[2] === 0x44 && header[3] === 0x46 && header[4] === 0x2d; if (isPdf) { setResolvedUrl(String(url)); setResolvedKind("pdf"); setLoading(false); return; } const textPreview = decodeTextPreview(buffer); if (textPreview != null) { setResolvedUrl(""); setResolvedText(textPreview); setResolvedKind("text"); setHint("Файл помечен как PDF, но не является валидным PDF. Показан текстовый предпросмотр."); setLoading(false); return; } throw new Error("Файл помечен как PDF, но не является валидным PDF-документом."); } if (kind === "text") { const textPreview = decodeTextPreview(buffer); if (textPreview == null) throw new Error("Не удалось распознать текстовый файл для предпросмотра."); setResolvedUrl(""); setResolvedText(textPreview); setResolvedKind("text"); setLoading(false); return; } const blob = new Blob([buffer], { type: response.headers.get("content-type") || mimeType || "application/octet-stream" }); objectUrl = URL.createObjectURL(blob); if (cancelled) { URL.revokeObjectURL(objectUrl); return; } setResolvedUrl(objectUrl); setResolvedKind(kind); setLoading(false); } catch (err) { if (cancelled) return; setError(err instanceof Error ? err.message : "Не удалось открыть предпросмотр"); setLoading(false); } })(); return () => { cancelled = true; if (objectUrl) URL.revokeObjectURL(objectUrl); }; }, [fileName, mimeType, open, url]); if (!open || !url) return null; const kind = resolvedKind || detectAttachmentPreviewKind(fileName, mimeType); return ( event.target.id === "request-file-preview-overlay" && onClose()}>
event.stopPropagation()}>

{title || fileName || "Предпросмотр файла"}

{loading ?

Загрузка предпросмотра...

: null} {!loading && !error && hint ?

{hint}

: null} {error ?

{error}

: null} {!loading && !error && kind === "image" && resolvedUrl ? ( {fileName ) : null} {!loading && !error && kind === "video" && resolvedUrl ? (