mirror of
https://github.com/TronoSfera/Law.git
synced 2026-05-18 10:03:45 +03:00
3221 lines
212 KiB
JavaScript
3221 lines
212 KiB
JavaScript
(() => {
|
|
// app/web/admin/shared/constants.js
|
|
var STATUS_LABELS = {
|
|
NEW: "\u041D\u043E\u0432\u0430\u044F",
|
|
IN_PROGRESS: "\u0412 \u0440\u0430\u0431\u043E\u0442\u0435",
|
|
WAITING_CLIENT: "\u041E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u043A\u043B\u0438\u0435\u043D\u0442\u0430",
|
|
WAITING_COURT: "\u041E\u0436\u0438\u0434\u0430\u043D\u0438\u0435 \u0441\u0443\u0434\u0430",
|
|
RESOLVED: "\u0420\u0435\u0448\u0435\u043D\u0430",
|
|
CLOSED: "\u0417\u0430\u043A\u0440\u044B\u0442\u0430",
|
|
REJECTED: "\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0430"
|
|
};
|
|
var INVOICE_STATUS_LABELS = {
|
|
WAITING_PAYMENT: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043E\u043F\u043B\u0430\u0442\u0443",
|
|
PAID: "\u041E\u043F\u043B\u0430\u0447\u0435\u043D",
|
|
CANCELED: "\u041E\u0442\u043C\u0435\u043D\u0435\u043D"
|
|
};
|
|
var TABLE_SERVER_CONFIG = {
|
|
requests: {
|
|
table: "requests",
|
|
// Requests use a specialized endpoint because it supports virtual/server-side filters
|
|
// (e.g. deadline alerts and unread notifications) that are not plain table columns.
|
|
endpoint: "/api/admin/requests/query",
|
|
sort: [{ field: "created_at", dir: "desc" }]
|
|
},
|
|
serviceRequests: {
|
|
table: "request_service_requests",
|
|
endpoint: "/api/admin/crud/request_service_requests/query",
|
|
sort: [{ field: "created_at", dir: "desc" }]
|
|
},
|
|
invoices: {
|
|
table: "invoices",
|
|
endpoint: "/api/admin/invoices/query",
|
|
sort: [{ field: "issued_at", dir: "desc" }]
|
|
},
|
|
quotes: {
|
|
table: "quotes",
|
|
endpoint: "/api/admin/crud/quotes/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
topics: {
|
|
table: "topics",
|
|
endpoint: "/api/admin/crud/topics/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
statuses: {
|
|
table: "statuses",
|
|
endpoint: "/api/admin/crud/statuses/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
formFields: {
|
|
table: "form_fields",
|
|
endpoint: "/api/admin/crud/form_fields/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
topicRequiredFields: {
|
|
table: "topic_required_fields",
|
|
endpoint: "/api/admin/crud/topic_required_fields/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
topicDataTemplates: {
|
|
table: "topic_data_templates",
|
|
endpoint: "/api/admin/crud/topic_data_templates/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
statusTransitions: {
|
|
table: "topic_status_transitions",
|
|
endpoint: "/api/admin/crud/topic_status_transitions/query",
|
|
sort: [{ field: "sort_order", dir: "asc" }]
|
|
},
|
|
users: {
|
|
table: "admin_users",
|
|
endpoint: "/api/admin/crud/admin_users/query",
|
|
sort: [{ field: "created_at", dir: "desc" }]
|
|
},
|
|
userTopics: {
|
|
table: "admin_user_topics",
|
|
endpoint: "/api/admin/crud/admin_user_topics/query",
|
|
sort: [{ field: "created_at", dir: "desc" }]
|
|
}
|
|
};
|
|
var TABLE_MUTATION_CONFIG = Object.fromEntries(
|
|
Object.entries(TABLE_SERVER_CONFIG).map(([tableKey, config]) => [
|
|
tableKey,
|
|
{
|
|
create: "/api/admin/crud/" + config.table,
|
|
update: (id) => "/api/admin/crud/" + config.table + "/" + id,
|
|
delete: (id) => "/api/admin/crud/" + config.table + "/" + id
|
|
}
|
|
])
|
|
);
|
|
TABLE_MUTATION_CONFIG.invoices = {
|
|
create: "/api/admin/invoices",
|
|
update: (id) => "/api/admin/invoices/" + id,
|
|
delete: (id) => "/api/admin/invoices/" + id
|
|
};
|
|
var TABLE_KEY_ALIASES = {
|
|
request_service_requests: "serviceRequests",
|
|
form_fields: "formFields",
|
|
status_groups: "statusGroups",
|
|
topic_required_fields: "topicRequiredFields",
|
|
topic_data_templates: "topicDataTemplates",
|
|
topic_status_transitions: "statusTransitions",
|
|
admin_users: "users",
|
|
admin_user_topics: "userTopics"
|
|
};
|
|
var TABLE_UNALIASES = Object.fromEntries(Object.entries(TABLE_KEY_ALIASES).map(([table, alias]) => [alias, table]));
|
|
|
|
// app/web/admin/shared/utils.js
|
|
function humanizeKey(value) {
|
|
const text = String(value || "").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
if (!text) return "-";
|
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
}
|
|
function statusLabel(code) {
|
|
return STATUS_LABELS[code] || code || "-";
|
|
}
|
|
function invoiceStatusLabel(code) {
|
|
return INVOICE_STATUS_LABELS[code] || code || "-";
|
|
}
|
|
function fmtDate(value) {
|
|
if (!value) return "-";
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const year = String(date.getFullYear()).slice(-2);
|
|
const hours = String(date.getHours()).padStart(2, "0");
|
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
return `${day}.${month}.${year} ${hours}:${minutes}`;
|
|
}
|
|
function fmtDateOnly(value) {
|
|
if (!value) return "-";
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const year = String(date.getFullYear()).slice(-2);
|
|
return `${day}.${month}.${year}`;
|
|
}
|
|
function fmtTimeOnly(value) {
|
|
if (!value) return "-";
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" });
|
|
}
|
|
function fmtShortDateTime(value) {
|
|
if (!value) return "-";
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const year = String(date.getFullYear()).slice(-2);
|
|
const hours = String(date.getHours()).padStart(2, "0");
|
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
return `${day}.${month}.${year} ${hours}:${minutes}`;
|
|
}
|
|
function fmtAmount(value) {
|
|
if (value == null || value === "") return "-";
|
|
const number = Number(value);
|
|
if (Number.isNaN(number)) return String(value);
|
|
return number.toLocaleString("ru-RU");
|
|
}
|
|
function fmtBytes(value) {
|
|
const size = Number(value || 0);
|
|
if (!Number.isFinite(size) || size <= 0) return "0 \u0411";
|
|
const units = ["\u0411", "\u041A\u0411", "\u041C\u0411", "\u0413\u0411"];
|
|
let normalized = size;
|
|
let index = 0;
|
|
while (normalized >= 1024 && index < units.length - 1) {
|
|
normalized /= 1024;
|
|
index += 1;
|
|
}
|
|
return normalized.toLocaleString("ru-RU", { maximumFractionDigits: index === 0 ? 0 : 1 }) + " " + units[index];
|
|
}
|
|
function detectAttachmentPreviewKind(fileName, mimeType) {
|
|
const name = String(fileName || "").toLowerCase();
|
|
const mime = String(mimeType || "").toLowerCase();
|
|
if (/\.(txt|md|csv|json|log|xml|ya?ml|ini|cfg)$/i.test(name)) return "text";
|
|
if (mime.startsWith("text/") || mime === "application/json" || mime === "application/xml" || mime === "text/xml") {
|
|
return "text";
|
|
}
|
|
if (mime.startsWith("image/") || /\.(png|jpe?g|gif|webp|bmp|svg)$/.test(name)) return "image";
|
|
if (mime.startsWith("video/") || /\.(mp4|webm|ogg|mov|m4v)$/.test(name)) return "video";
|
|
if (mime === "application/pdf" || /\.pdf$/.test(name)) return "pdf";
|
|
return "none";
|
|
}
|
|
|
|
// app/web/admin/features/requests/RequestWorkspace.jsx
|
|
function RequestWorkspace({
|
|
viewerRole,
|
|
viewerUserId,
|
|
loading,
|
|
trackNumber,
|
|
requestData,
|
|
financeSummary,
|
|
invoices,
|
|
statusRouteNodes,
|
|
statusHistory,
|
|
availableStatuses,
|
|
currentImportantDateAt,
|
|
pendingStatusChangePreset,
|
|
messages,
|
|
attachments,
|
|
messageDraft,
|
|
selectedFiles,
|
|
fileUploading,
|
|
status,
|
|
onMessageChange,
|
|
onSendMessage,
|
|
onFilesSelect,
|
|
onRemoveSelectedFile,
|
|
onClearSelectedFiles,
|
|
onLoadRequestDataTemplates,
|
|
onLoadRequestDataBatch,
|
|
onLoadRequestDataTemplateDetails,
|
|
onSaveRequestDataTemplate,
|
|
onSaveRequestDataBatch,
|
|
onIssueInvoice,
|
|
onDownloadInvoicePdf,
|
|
onSaveRequestDataValues,
|
|
onUploadRequestAttachment,
|
|
onChangeStatus,
|
|
onConsumePendingStatusChangePreset,
|
|
onLiveProbe,
|
|
onTypingSignal,
|
|
domIds,
|
|
AttachmentPreviewModalComponent,
|
|
StatusLineComponent
|
|
}) {
|
|
var _a, _b, _c;
|
|
const { useEffect, useMemo, useRef, useState } = React;
|
|
const [preview, setPreview] = useState({ open: false, url: "", fileName: "", mimeType: "" });
|
|
const [chatTab, setChatTab] = useState("chat");
|
|
const [dropActive, setDropActive] = useState(false);
|
|
const [financeOpen, setFinanceOpen] = useState(false);
|
|
const [financeIssueForm, setFinanceIssueForm] = useState({
|
|
open: false,
|
|
saving: false,
|
|
amount: "",
|
|
serviceDescription: "",
|
|
payerDisplayName: "",
|
|
error: ""
|
|
});
|
|
const [requestDataListOpen, setRequestDataListOpen] = useState(false);
|
|
const [descriptionOpen, setDescriptionOpen] = useState(false);
|
|
const [requestTemplateSuggestOpen, setRequestTemplateSuggestOpen] = useState(false);
|
|
const [catalogFieldSuggestOpen, setCatalogFieldSuggestOpen] = useState(false);
|
|
const [statusChangeModal, setStatusChangeModal] = useState({
|
|
open: false,
|
|
saving: false,
|
|
statusCode: "",
|
|
allowedStatusCodes: null,
|
|
importantDateAt: "",
|
|
comment: "",
|
|
files: [],
|
|
error: ""
|
|
});
|
|
const [draggedRequestRowId, setDraggedRequestRowId] = useState("");
|
|
const [dragOverRequestRowId, setDragOverRequestRowId] = useState("");
|
|
const [dataRequestModal, setDataRequestModal] = useState({
|
|
open: false,
|
|
loading: false,
|
|
saving: false,
|
|
savingTemplate: false,
|
|
messageId: "",
|
|
documentName: "",
|
|
availableDocuments: [],
|
|
templateList: [],
|
|
requestTemplateQuery: "",
|
|
templateName: "",
|
|
selectedRequestTemplateId: "",
|
|
templates: [],
|
|
catalogFieldQuery: "",
|
|
selectedCatalogTemplateId: "",
|
|
rows: [],
|
|
customLabel: "",
|
|
customType: "string",
|
|
templateStatus: "",
|
|
error: ""
|
|
});
|
|
const [clientDataModal, setClientDataModal] = useState({
|
|
open: false,
|
|
loading: false,
|
|
saving: false,
|
|
messageId: "",
|
|
items: [],
|
|
status: "",
|
|
error: ""
|
|
});
|
|
const [composerFocused, setComposerFocused] = useState(false);
|
|
const [typingPeers, setTypingPeers] = useState([]);
|
|
const [liveMode, setLiveMode] = useState("online");
|
|
const fileInputRef = useRef(null);
|
|
const statusChangeFileInputRef = useRef(null);
|
|
const chatListRef = useRef(null);
|
|
const liveCursorRef = useRef("");
|
|
const liveTimerRef = useRef(null);
|
|
const liveInFlightRef = useRef(false);
|
|
const liveFailCountRef = useRef(0);
|
|
const typingHeartbeatRef = useRef(null);
|
|
const typingActiveRef = useRef(false);
|
|
const lastAutoScrollCursorRef = useRef("");
|
|
const idMap = useMemo(
|
|
() => ({
|
|
messagesList: "request-modal-messages",
|
|
filesList: "request-modal-files",
|
|
messageBody: "request-modal-message-body",
|
|
sendButton: "request-modal-message-send",
|
|
fileInput: "request-modal-file-input",
|
|
fileUploadButton: "",
|
|
dataRequestOverlay: "data-request-overlay",
|
|
dataRequestItems: "data-request-items",
|
|
dataRequestStatus: "data-request-status",
|
|
dataRequestSave: "data-request-save",
|
|
...domIds || {}
|
|
}),
|
|
[domIds]
|
|
);
|
|
const requestDataTypeOptions = useMemo(
|
|
() => [
|
|
{ value: "string", label: "\u0421\u0442\u0440\u043E\u043A\u0430" },
|
|
{ value: "date", label: "\u0414\u0430\u0442\u0430" },
|
|
{ value: "number", label: "\u0427\u0438\u0441\u043B\u043E" },
|
|
{ value: "file", label: "\u0424\u0430\u0439\u043B" },
|
|
{ value: "text", label: "\u0422\u0435\u043A\u0441\u0442" }
|
|
],
|
|
[]
|
|
);
|
|
const openPreview = (item) => {
|
|
if (!(item == null ? void 0 : item.download_url)) return;
|
|
setPreview({
|
|
open: true,
|
|
url: String(item.download_url),
|
|
fileName: String(item.file_name || ""),
|
|
mimeType: String(item.mime_type || "")
|
|
});
|
|
};
|
|
const closePreview = () => setPreview({ open: false, url: "", fileName: "", mimeType: "" });
|
|
const pendingFiles = Array.isArray(selectedFiles) ? selectedFiles : [];
|
|
const hasPendingFiles = pendingFiles.length > 0;
|
|
const canSubmit = Boolean(String(messageDraft || "").trim() || hasPendingFiles);
|
|
const onInputFiles = (event) => {
|
|
const files = Array.from(event.target && event.target.files || []);
|
|
if (files.length && typeof onFilesSelect === "function") onFilesSelect(files);
|
|
event.target.value = "";
|
|
};
|
|
const onDropFiles = (event) => {
|
|
event.preventDefault();
|
|
setDropActive(false);
|
|
const files = Array.from(event.dataTransfer && event.dataTransfer.files || []);
|
|
if (files.length && typeof onFilesSelect === "function") onFilesSelect(files);
|
|
};
|
|
const row = requestData && typeof requestData === "object" ? requestData : null;
|
|
const finance = financeSummary && typeof financeSummary === "object" ? financeSummary : null;
|
|
const viewerRoleCode = String(viewerRole || "").toUpperCase();
|
|
const canRequestData = viewerRoleCode === "LAWYER" || viewerRoleCode === "ADMIN";
|
|
const canFillRequestData = viewerRoleCode === "CLIENT";
|
|
const canSeeRate = viewerRoleCode !== "CLIENT";
|
|
const canSeeCreatedUpdatedInCard = viewerRoleCode !== "CLIENT";
|
|
const showTopicStatusInCard = viewerRoleCode !== "CLIENT";
|
|
const showContactsInCard = viewerRoleCode !== "CLIENT";
|
|
const safeMessages = Array.isArray(messages) ? messages : [];
|
|
const safeAttachments = Array.isArray(attachments) ? attachments : [];
|
|
const safeInvoices = Array.isArray(invoices) ? invoices : [];
|
|
const safeStatusHistory = Array.isArray(statusHistory) ? statusHistory : [];
|
|
const safeAvailableStatuses = Array.isArray(availableStatuses) ? availableStatuses : [];
|
|
const totalFilesBytes = safeAttachments.reduce((acc, item) => acc + Number((item == null ? void 0 : item.size_bytes) || 0), 0);
|
|
const clientLabel = (row == null ? void 0 : row.client_name) || "-";
|
|
const clientPhone = String((row == null ? void 0 : row.client_phone) || "").trim();
|
|
const lawyerLabel = (row == null ? void 0 : row.assigned_lawyer_name) || (row == null ? void 0 : row.assigned_lawyer_id) || "\u041D\u0435 \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D";
|
|
const lawyerPhone = String((row == null ? void 0 : row.assigned_lawyer_phone) || "").trim();
|
|
const clientHasPhone = Boolean(clientPhone);
|
|
const lawyerHasPhone = Boolean(lawyerPhone);
|
|
const messagePlaceholder = canFillRequestData ? "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0434\u043B\u044F \u044E\u0440\u0438\u0441\u0442\u0430" : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0430";
|
|
const selectedRequestTemplateCandidate = useMemo(
|
|
() => (dataRequestModal.templateList || []).find((item) => {
|
|
const query = String(dataRequestModal.requestTemplateQuery || "").trim().toLowerCase();
|
|
if (!query) return false;
|
|
return query === String((item == null ? void 0 : item.name) || "").trim().toLowerCase() || query === String((item == null ? void 0 : item.id) || "").trim().toLowerCase();
|
|
}) || null,
|
|
[dataRequestModal.requestTemplateQuery, dataRequestModal.templateList]
|
|
);
|
|
const selectedCatalogFieldCandidate = useMemo(
|
|
() => (dataRequestModal.templates || []).find((item) => {
|
|
const query = String(dataRequestModal.catalogFieldQuery || "").trim().toLowerCase();
|
|
if (!query) return false;
|
|
return query === String((item == null ? void 0 : item.label) || "").trim().toLowerCase() || query === String((item == null ? void 0 : item.key) || "").trim().toLowerCase() || query === String((item == null ? void 0 : item.id) || "").trim().toLowerCase();
|
|
}) || null,
|
|
[dataRequestModal.catalogFieldQuery, dataRequestModal.templates]
|
|
);
|
|
const filteredRequestTemplates = useMemo(() => {
|
|
const query = String(dataRequestModal.requestTemplateQuery || "").trim().toLowerCase();
|
|
const rows = Array.isArray(dataRequestModal.templateList) ? dataRequestModal.templateList : [];
|
|
if (!query) return rows.slice(0, 8);
|
|
return rows.filter((item) => String((item == null ? void 0 : item.name) || "").toLowerCase().includes(query)).slice(0, 8);
|
|
}, [dataRequestModal.requestTemplateQuery, dataRequestModal.templateList]);
|
|
const filteredCatalogFields = useMemo(() => {
|
|
const query = String(dataRequestModal.catalogFieldQuery || "").trim().toLowerCase();
|
|
const rows = Array.isArray(dataRequestModal.templates) ? dataRequestModal.templates : [];
|
|
if (!query) return rows.slice(0, 10);
|
|
return rows.filter((item) => {
|
|
const label = String((item == null ? void 0 : item.label) || "").toLowerCase();
|
|
const key = String((item == null ? void 0 : item.key) || "").toLowerCase();
|
|
return label.includes(query) || key.includes(query);
|
|
}).slice(0, 10);
|
|
}, [dataRequestModal.catalogFieldQuery, dataRequestModal.templates]);
|
|
const requestTemplateActionMode = selectedRequestTemplateCandidate ? "save" : String(dataRequestModal.requestTemplateQuery || "").trim() ? "create" : "";
|
|
const catalogFieldActionMode = selectedCatalogFieldCandidate ? "add" : String(dataRequestModal.catalogFieldQuery || "").trim() ? "create" : "";
|
|
const requestTemplateBadge = useMemo(() => {
|
|
const query = String(dataRequestModal.requestTemplateQuery || "").trim();
|
|
if (!query) return null;
|
|
const matched = selectedRequestTemplateCandidate;
|
|
if (!matched) return { kind: "create", label: "\u041D\u043E\u0432\u044B\u0439 \u0448\u0430\u0431\u043B\u043E\u043D" };
|
|
const roleCode = String(viewerRole || "").toUpperCase();
|
|
const actorId = String(viewerUserId || "").trim();
|
|
const ownerId = String(matched.created_by_admin_id || "").trim();
|
|
if (roleCode === "LAWYER" && ownerId && actorId && ownerId !== actorId) {
|
|
return { kind: "readonly", label: "\u0427\u0443\u0436\u043E\u0439 \u0448\u0430\u0431\u043B\u043E\u043D" };
|
|
}
|
|
return { kind: "existing", label: "\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 \u0448\u0430\u0431\u043B\u043E\u043D" };
|
|
}, [dataRequestModal.requestTemplateQuery, selectedRequestTemplateCandidate, viewerRole, viewerUserId]);
|
|
const canSaveSelectedRequestTemplate = useMemo(() => {
|
|
if (!String(dataRequestModal.requestTemplateQuery || "").trim()) return false;
|
|
if (!requestTemplateBadge) return true;
|
|
return requestTemplateBadge.kind !== "readonly";
|
|
}, [dataRequestModal.requestTemplateQuery, requestTemplateBadge]);
|
|
const attachmentById = useMemo(() => {
|
|
const map = /* @__PURE__ */ new Map();
|
|
safeAttachments.forEach((item) => {
|
|
const id = String((item == null ? void 0 : item.id) || "").trim();
|
|
if (id) map.set(id, item);
|
|
});
|
|
return map;
|
|
}, [safeAttachments]);
|
|
const statusOptions = useMemo(
|
|
() => safeAvailableStatuses.filter((item) => item && item.code).map((item) => ({
|
|
code: String(item.code),
|
|
name: String(item.name || "").trim() || humanizeKey(item.code),
|
|
groupName: item.status_group_name ? String(item.status_group_name) : "",
|
|
isTerminal: Boolean(item.is_terminal)
|
|
})),
|
|
[safeAvailableStatuses]
|
|
);
|
|
const statusByCode = useMemo(() => new Map(statusOptions.map((item) => [item.code, item])), [statusOptions]);
|
|
const toDateTimeLocalValue = (value) => {
|
|
if (!value) return "";
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) return "";
|
|
const pad = (n) => String(n).padStart(2, "0");
|
|
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + "T" + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
};
|
|
const defaultImportantDateLocal = useMemo(() => {
|
|
const source = String(currentImportantDateAt || (row == null ? void 0 : row.important_date_at) || "").trim();
|
|
if (source) {
|
|
const local = toDateTimeLocalValue(source);
|
|
if (local) return local;
|
|
}
|
|
const next = new Date(Date.now() + 3 * 24 * 60 * 60 * 1e3);
|
|
return toDateTimeLocalValue(next.toISOString());
|
|
}, [currentImportantDateAt, row == null ? void 0 : row.important_date_at]);
|
|
const formatDuration = (seconds) => {
|
|
const total = Number(seconds);
|
|
if (!Number.isFinite(total) || total < 0) return "\u2014";
|
|
const days = Math.floor(total / 86400);
|
|
const hours = Math.floor(total % 86400 / 3600);
|
|
const minutes = Math.floor(total % 3600 / 60);
|
|
if (days > 0) return days + " \u0434 " + hours + " \u0447";
|
|
if (hours > 0) return hours + " \u0447 " + minutes + " \u043C\u0438\u043D";
|
|
return Math.max(0, minutes) + " \u043C\u0438\u043D";
|
|
};
|
|
const formatMoneyInput = (value) => {
|
|
const amount = Number(value);
|
|
if (!Number.isFinite(amount) || amount <= 0) return "";
|
|
return String(Math.round((amount + Number.EPSILON) * 100) / 100);
|
|
};
|
|
const openFinanceIssueForm = () => {
|
|
var _a2, _b2, _c2, _d, _e;
|
|
const defaultAmount = (_e = (_d = (_c2 = (_b2 = (_a2 = finance == null ? void 0 : finance.request_cost) != null ? _a2 : row == null ? void 0 : row.request_cost) != null ? _b2 : row == null ? void 0 : row.invoice_amount) != null ? _c2 : finance == null ? void 0 : finance.effective_rate) != null ? _d : row == null ? void 0 : row.effective_rate) != null ? _e : "";
|
|
setFinanceIssueForm({
|
|
open: true,
|
|
saving: false,
|
|
amount: formatMoneyInput(defaultAmount),
|
|
serviceDescription: String((row == null ? void 0 : row.topic_name) || (row == null ? void 0 : row.topic_code) || "\u042E\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0443\u0441\u043B\u0443\u0433\u0438"),
|
|
payerDisplayName: String((row == null ? void 0 : row.client_name) || "").trim() || "\u041A\u043B\u0438\u0435\u043D\u0442",
|
|
error: ""
|
|
});
|
|
};
|
|
const closeFinanceIssueForm = () => {
|
|
setFinanceIssueForm((prev) => ({ ...prev, open: false, saving: false, error: "" }));
|
|
};
|
|
const closeFinanceModal = () => {
|
|
setFinanceOpen(false);
|
|
closeFinanceIssueForm();
|
|
};
|
|
const submitFinanceIssueForm = async (event) => {
|
|
if (event && typeof event.preventDefault === "function") event.preventDefault();
|
|
if (!(row == null ? void 0 : row.id) || typeof onIssueInvoice !== "function") return;
|
|
const normalizedAmount = Number(String(financeIssueForm.amount || "").replace(",", "."));
|
|
if (!Number.isFinite(normalizedAmount) || normalizedAmount <= 0) {
|
|
setFinanceIssueForm((prev) => ({ ...prev, error: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u0443\u044E \u0441\u0443\u043C\u043C\u0443 \u0441\u0447\u0435\u0442\u0430" }));
|
|
return;
|
|
}
|
|
setFinanceIssueForm((prev) => ({ ...prev, saving: true, error: "" }));
|
|
try {
|
|
await onIssueInvoice({
|
|
requestId: String(row.id),
|
|
amount: normalizedAmount,
|
|
serviceDescription: String(financeIssueForm.serviceDescription || ""),
|
|
payerDisplayName: String(financeIssueForm.payerDisplayName || "")
|
|
});
|
|
setFinanceIssueForm((prev) => ({ ...prev, open: false, saving: false, error: "" }));
|
|
} catch (error) {
|
|
setFinanceIssueForm((prev) => ({ ...prev, saving: false, error: (error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0447\u0435\u0442" }));
|
|
}
|
|
};
|
|
const openStatusChangeModal = (preset) => {
|
|
const suggested = Array.isArray(preset == null ? void 0 : preset.suggestedStatuses) ? preset.suggestedStatuses.filter(Boolean) : [];
|
|
const currentCode = String((row == null ? void 0 : row.status_code) || "").trim();
|
|
const firstSuggested = suggested.find((code) => code && code !== currentCode) || "";
|
|
setStatusChangeModal({
|
|
open: true,
|
|
saving: false,
|
|
statusCode: firstSuggested,
|
|
allowedStatusCodes: suggested.length ? suggested : null,
|
|
importantDateAt: defaultImportantDateLocal,
|
|
comment: "",
|
|
files: [],
|
|
error: ""
|
|
});
|
|
};
|
|
const closeStatusChangeModal = () => {
|
|
setStatusChangeModal((prev) => ({ ...prev, open: false, saving: false, error: "", files: [] }));
|
|
};
|
|
useEffect(() => {
|
|
if (!pendingStatusChangePreset) return;
|
|
openStatusChangeModal(pendingStatusChangePreset);
|
|
if (typeof onConsumePendingStatusChangePreset === "function") onConsumePendingStatusChangePreset();
|
|
}, [pendingStatusChangePreset]);
|
|
const requestDataListItems = useMemo(() => {
|
|
const byKey = /* @__PURE__ */ new Map();
|
|
const messagesChrono = [...safeMessages].sort((a, b) => {
|
|
const at = new Date((a == null ? void 0 : a.created_at) || 0).getTime();
|
|
const bt = new Date((b == null ? void 0 : b.created_at) || 0).getTime();
|
|
if (at !== bt) return at - bt;
|
|
return String((a == null ? void 0 : a.id) || "").localeCompare(String((b == null ? void 0 : b.id) || ""), "ru");
|
|
});
|
|
messagesChrono.forEach((msg) => {
|
|
if (String((msg == null ? void 0 : msg.message_kind) || "") !== "REQUEST_DATA") return;
|
|
const items = Array.isArray(msg == null ? void 0 : msg.request_data_items) ? msg.request_data_items : [];
|
|
items.forEach((item, idx) => {
|
|
const key = String((item == null ? void 0 : item.key) || (item == null ? void 0 : item.id) || "item-" + idx);
|
|
if (!key) return;
|
|
byKey.set(key, {
|
|
id: String((item == null ? void 0 : item.id) || ""),
|
|
key,
|
|
label: String((item == null ? void 0 : item.label) || (item == null ? void 0 : item.label_short) || key),
|
|
field_type: String((item == null ? void 0 : item.field_type) || "string").toLowerCase(),
|
|
value_text: (item == null ? void 0 : item.value_text) == null ? "" : String(item.value_text),
|
|
is_filled: Boolean(item == null ? void 0 : item.is_filled),
|
|
source_message_id: String((msg == null ? void 0 : msg.id) || ""),
|
|
source_message_created_at: (msg == null ? void 0 : msg.created_at) || null,
|
|
value_file: (item == null ? void 0 : item.value_file) || null
|
|
});
|
|
});
|
|
});
|
|
return Array.from(byKey.values()).sort((a, b) => {
|
|
const aFilled = a.is_filled ? 1 : 0;
|
|
const bFilled = b.is_filled ? 1 : 0;
|
|
if (aFilled !== bFilled) return aFilled - bFilled;
|
|
return String(a.label || a.key).localeCompare(String(b.label || b.key), "ru");
|
|
});
|
|
}, [safeMessages]);
|
|
const attachmentsByMessageId = useMemo(() => {
|
|
const map = /* @__PURE__ */ new Map();
|
|
safeAttachments.forEach((item) => {
|
|
const messageId = String((item == null ? void 0 : item.message_id) || "").trim();
|
|
if (!messageId) return;
|
|
if (!map.has(messageId)) map.set(messageId, []);
|
|
map.get(messageId).push(item);
|
|
});
|
|
return map;
|
|
}, [safeAttachments]);
|
|
const localActivityCursor = useMemo(() => {
|
|
let latestTs = 0;
|
|
const pickLatest = (value) => {
|
|
if (!value) return;
|
|
const ts = new Date(value).getTime();
|
|
if (Number.isFinite(ts) && ts > latestTs) latestTs = ts;
|
|
};
|
|
safeMessages.forEach((item) => {
|
|
pickLatest(item == null ? void 0 : item.updated_at);
|
|
pickLatest(item == null ? void 0 : item.created_at);
|
|
});
|
|
safeAttachments.forEach((item) => {
|
|
pickLatest(item == null ? void 0 : item.updated_at);
|
|
pickLatest(item == null ? void 0 : item.created_at);
|
|
});
|
|
return latestTs > 0 ? new Date(latestTs).toISOString() : "";
|
|
}, [safeAttachments, safeMessages]);
|
|
const typingHintText = useMemo(() => {
|
|
const rows = Array.isArray(typingPeers) ? typingPeers : [];
|
|
if (!rows.length) return "";
|
|
const labels = rows.map((item) => String((item == null ? void 0 : item.actor_label) || (item == null ? void 0 : item.label) || "").trim()).filter(Boolean);
|
|
if (!labels.length) return "\u0421\u043E\u0431\u0435\u0441\u0435\u0434\u043D\u0438\u043A \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442...";
|
|
const unique = [];
|
|
labels.forEach((label) => {
|
|
if (!unique.includes(label)) unique.push(label);
|
|
});
|
|
if (unique.length === 1) return unique[0] + " \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442...";
|
|
if (unique.length === 2) return unique[0] + " \u0438 " + unique[1] + " \u043F\u0435\u0447\u0430\u0442\u0430\u044E\u0442...";
|
|
return unique[0] + ", " + unique[1] + " \u0438 \u0435\u0449\u0435 " + String(unique.length - 2) + " \u043F\u0435\u0447\u0430\u0442\u0430\u044E\u0442...";
|
|
}, [typingPeers]);
|
|
const openAttachmentFromMessage = (item) => {
|
|
if (!(item == null ? void 0 : item.download_url)) return;
|
|
const kind = detectAttachmentPreviewKind(item.file_name, item.mime_type);
|
|
if (kind === "none") {
|
|
window.open(String(item.download_url), "_blank", "noopener,noreferrer");
|
|
return;
|
|
}
|
|
openPreview(item);
|
|
};
|
|
const downloadAttachment = (item) => {
|
|
const url = String((item == null ? void 0 : item.download_url) || "").trim();
|
|
if (!url) return;
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.target = "_blank";
|
|
link.rel = "noreferrer";
|
|
const fileName = String((item == null ? void 0 : item.file_name) || "").trim();
|
|
if (fileName) link.download = fileName;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
};
|
|
useEffect(() => {
|
|
liveCursorRef.current = localActivityCursor || "";
|
|
}, [localActivityCursor, row == null ? void 0 : row.id]);
|
|
useEffect(() => {
|
|
if (!row || typeof onLiveProbe !== "function") {
|
|
setTypingPeers([]);
|
|
setLiveMode("online");
|
|
if (liveTimerRef.current) {
|
|
clearTimeout(liveTimerRef.current);
|
|
liveTimerRef.current = null;
|
|
}
|
|
liveInFlightRef.current = false;
|
|
liveFailCountRef.current = 0;
|
|
return void 0;
|
|
}
|
|
let cancelled = false;
|
|
const scheduleNext = (ms) => {
|
|
if (cancelled) return;
|
|
if (liveTimerRef.current) clearTimeout(liveTimerRef.current);
|
|
liveTimerRef.current = setTimeout(runProbe, ms);
|
|
};
|
|
const runProbe = async () => {
|
|
if (cancelled || liveInFlightRef.current) return;
|
|
liveInFlightRef.current = true;
|
|
try {
|
|
const payload = await onLiveProbe({ cursor: liveCursorRef.current });
|
|
const cursor = String((payload == null ? void 0 : payload.cursor) || "").trim();
|
|
if (cursor) liveCursorRef.current = cursor;
|
|
setTypingPeers(Array.isArray(payload == null ? void 0 : payload.typing) ? payload.typing : []);
|
|
liveFailCountRef.current = 0;
|
|
setLiveMode("online");
|
|
} catch (_) {
|
|
liveFailCountRef.current += 1;
|
|
setLiveMode(liveFailCountRef.current >= 3 ? "degraded" : "online");
|
|
} finally {
|
|
liveInFlightRef.current = false;
|
|
const hidden = typeof document !== "undefined" && document.visibilityState === "hidden";
|
|
const baseInterval = hidden ? 8e3 : 2500;
|
|
const failStep = Math.min(5, Math.max(0, liveFailCountRef.current));
|
|
const backoffInterval = failStep > 0 ? Math.min(3e4, baseInterval * Math.pow(2, failStep - 1)) : baseInterval;
|
|
scheduleNext(backoffInterval);
|
|
}
|
|
};
|
|
runProbe();
|
|
return () => {
|
|
cancelled = true;
|
|
if (liveTimerRef.current) {
|
|
clearTimeout(liveTimerRef.current);
|
|
liveTimerRef.current = null;
|
|
}
|
|
liveInFlightRef.current = false;
|
|
liveFailCountRef.current = 0;
|
|
setTypingPeers([]);
|
|
setLiveMode("online");
|
|
};
|
|
}, [onLiveProbe, row, trackNumber]);
|
|
const typingEnabled = Boolean(
|
|
row && typeof onTypingSignal === "function" && !loading && !fileUploading && composerFocused && String(messageDraft || "").trim()
|
|
);
|
|
useEffect(() => {
|
|
if (typeof onTypingSignal !== "function" || !row) {
|
|
if (typingHeartbeatRef.current) {
|
|
clearInterval(typingHeartbeatRef.current);
|
|
typingHeartbeatRef.current = null;
|
|
}
|
|
typingActiveRef.current = false;
|
|
return;
|
|
}
|
|
if (typingEnabled) {
|
|
if (!typingActiveRef.current) {
|
|
typingActiveRef.current = true;
|
|
void onTypingSignal({ typing: true }).catch(() => null);
|
|
}
|
|
if (!typingHeartbeatRef.current) {
|
|
typingHeartbeatRef.current = setInterval(() => {
|
|
void onTypingSignal({ typing: true }).catch(() => null);
|
|
}, 2500);
|
|
}
|
|
return;
|
|
}
|
|
if (typingHeartbeatRef.current) {
|
|
clearInterval(typingHeartbeatRef.current);
|
|
typingHeartbeatRef.current = null;
|
|
}
|
|
if (typingActiveRef.current) {
|
|
typingActiveRef.current = false;
|
|
void onTypingSignal({ typing: false }).catch(() => null);
|
|
}
|
|
}, [onTypingSignal, row, typingEnabled]);
|
|
useEffect(
|
|
() => () => {
|
|
if (typingHeartbeatRef.current) {
|
|
clearInterval(typingHeartbeatRef.current);
|
|
typingHeartbeatRef.current = null;
|
|
}
|
|
if (typingActiveRef.current && typeof onTypingSignal === "function") {
|
|
typingActiveRef.current = false;
|
|
void onTypingSignal({ typing: false }).catch(() => null);
|
|
}
|
|
},
|
|
[onTypingSignal]
|
|
);
|
|
const newDataRequestRow = (source) => {
|
|
const item = source || {};
|
|
const label = String(item.label || "").trim();
|
|
const key = String(item.key || "").trim();
|
|
const fieldTypeRaw = String(item.field_type || item.value_type || "string").trim().toLowerCase();
|
|
const fieldType = ["string", "text", "date", "number", "file"].includes(fieldTypeRaw) ? fieldTypeRaw : "string";
|
|
return {
|
|
localId: "row-" + Math.random().toString(36).slice(2),
|
|
id: item.id ? String(item.id) : "",
|
|
topic_template_id: item.topic_template_id ? String(item.topic_template_id) : item.id ? String(item.id) : "",
|
|
key,
|
|
label: label || "\u041F\u043E\u043B\u0435",
|
|
field_type: fieldType,
|
|
document_name: String(item.document_name || "").trim(),
|
|
value_text: item.value_text == null ? "" : String(item.value_text),
|
|
value_file: item.value_file || null,
|
|
is_filled: Boolean(item.is_filled)
|
|
};
|
|
};
|
|
const getRequestDataRowIdentity = (item) => {
|
|
const rowItem = item || {};
|
|
const key = String(rowItem.key || "").trim().toLowerCase();
|
|
if (key) return "key:" + key;
|
|
const tplId = String(rowItem.topic_template_id || rowItem.id || "").trim();
|
|
if (tplId) return "tpl:" + tplId;
|
|
return "label:" + String(rowItem.label || "").trim().toLowerCase();
|
|
};
|
|
const mergeRequestDataRows = (baseRows, incomingRows) => {
|
|
const rows = Array.isArray(baseRows) ? [...baseRows] : [];
|
|
const nextItems = Array.isArray(incomingRows) ? incomingRows : [];
|
|
const seen = new Set(rows.map((rowItem) => getRequestDataRowIdentity(rowItem)));
|
|
nextItems.forEach((rowItem) => {
|
|
const identity = getRequestDataRowIdentity(rowItem);
|
|
if (!identity || seen.has(identity)) return;
|
|
seen.add(identity);
|
|
rows.push(rowItem);
|
|
});
|
|
return rows;
|
|
};
|
|
const openCreateDataRequestModal = async () => {
|
|
if (!canRequestData || typeof onLoadRequestDataTemplates !== "function") return;
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
open: true,
|
|
loading: true,
|
|
saving: false,
|
|
savingTemplate: false,
|
|
messageId: "",
|
|
rows: [],
|
|
error: "",
|
|
templateStatus: "",
|
|
requestTemplateQuery: "",
|
|
catalogFieldQuery: "",
|
|
selectedCatalogTemplateId: "",
|
|
selectedRequestTemplateId: "",
|
|
templateName: "",
|
|
documentName: "",
|
|
customLabel: "",
|
|
customType: "string"
|
|
}));
|
|
try {
|
|
const data = await onLoadRequestDataTemplates();
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
open: true,
|
|
loading: false,
|
|
templates: Array.isArray(data == null ? void 0 : data.rows) ? data.rows : [],
|
|
templateList: Array.isArray(data == null ? void 0 : data.templates) ? data.templates : [],
|
|
availableDocuments: Array.isArray(data == null ? void 0 : data.documents) ? data.documents : [],
|
|
documentName: "",
|
|
requestTemplateQuery: "",
|
|
catalogFieldQuery: ""
|
|
}));
|
|
} catch (error) {
|
|
setDataRequestModal((prev) => ({ ...prev, loading: false, error: error.message || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0448\u0430\u0431\u043B\u043E\u043D\u044B" }));
|
|
}
|
|
};
|
|
const openEditDataRequestModal = async (messageId) => {
|
|
if (!canRequestData || !messageId) return;
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
open: true,
|
|
loading: true,
|
|
saving: false,
|
|
savingTemplate: false,
|
|
messageId: String(messageId),
|
|
rows: [],
|
|
error: "",
|
|
templateStatus: "",
|
|
requestTemplateQuery: "",
|
|
catalogFieldQuery: "",
|
|
selectedCatalogTemplateId: "",
|
|
selectedRequestTemplateId: "",
|
|
templateName: ""
|
|
}));
|
|
try {
|
|
const [batch, templates] = await Promise.all([
|
|
typeof onLoadRequestDataBatch === "function" ? onLoadRequestDataBatch(messageId) : Promise.resolve({ items: [] }),
|
|
typeof onLoadRequestDataTemplates === "function" ? onLoadRequestDataTemplates() : Promise.resolve({ rows: [], documents: [], templates: [] })
|
|
]);
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
open: true,
|
|
loading: false,
|
|
messageId: String(messageId),
|
|
rows: Array.isArray(batch == null ? void 0 : batch.items) ? batch.items.map(newDataRequestRow) : [],
|
|
documentName: String((batch == null ? void 0 : batch.document_name) || ""),
|
|
templates: Array.isArray(templates == null ? void 0 : templates.rows) ? templates.rows : [],
|
|
templateList: Array.isArray(templates == null ? void 0 : templates.templates) ? templates.templates : [],
|
|
availableDocuments: Array.isArray(templates == null ? void 0 : templates.documents) ? templates.documents : [],
|
|
requestTemplateQuery: "",
|
|
catalogFieldQuery: ""
|
|
}));
|
|
} catch (error) {
|
|
setDataRequestModal((prev) => ({ ...prev, loading: false, error: error.message || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0437\u0430\u043F\u0440\u043E\u0441" }));
|
|
}
|
|
};
|
|
const closeDataRequestModal = () => {
|
|
setDataRequestModal((prev) => ({ ...prev, open: false, error: "", saving: false, savingTemplate: false, templateStatus: "" }));
|
|
};
|
|
const findRequestTemplateByQuery = (queryValue) => {
|
|
const query = String(queryValue || "").trim().toLowerCase();
|
|
if (!query) return null;
|
|
return (dataRequestModal.templateList || []).find((item) => {
|
|
const id = String((item == null ? void 0 : item.id) || "").toLowerCase();
|
|
const name = String((item == null ? void 0 : item.name) || "").toLowerCase();
|
|
return query === id || query === name;
|
|
}) || null;
|
|
};
|
|
const findCatalogFieldByQuery = (queryValue) => {
|
|
const query = String(queryValue || "").trim().toLowerCase();
|
|
if (!query) return null;
|
|
return (dataRequestModal.templates || []).find((item) => {
|
|
const id = String((item == null ? void 0 : item.id) || "").toLowerCase();
|
|
const key = String((item == null ? void 0 : item.key) || "").toLowerCase();
|
|
const label = String((item == null ? void 0 : item.label) || "").toLowerCase();
|
|
return query === id || query === key || query === label;
|
|
}) || null;
|
|
};
|
|
const applyRequestTemplateById = async (rawTemplateId, templateNameHint) => {
|
|
if (typeof onLoadRequestDataTemplateDetails !== "function") return;
|
|
const templateId = String(rawTemplateId || "").trim();
|
|
if (!templateId) return;
|
|
setDataRequestModal((prev) => ({ ...prev, loading: true, error: "" }));
|
|
try {
|
|
const data = await onLoadRequestDataTemplateDetails(templateId);
|
|
const incomingRows = (Array.isArray(data == null ? void 0 : data.items) ? data.items : []).map(
|
|
(item) => newDataRequestRow({
|
|
...item,
|
|
topic_template_id: item.topic_data_template_id || item.topic_template_id || "",
|
|
field_type: item.value_type || item.field_type
|
|
})
|
|
);
|
|
setDataRequestModal((prev) => {
|
|
var _a2, _b2;
|
|
return {
|
|
...prev,
|
|
loading: false,
|
|
rows: mergeRequestDataRows(prev.rows, incomingRows),
|
|
selectedRequestTemplateId: String(((_a2 = data == null ? void 0 : data.template) == null ? void 0 : _a2.id) || prev.selectedRequestTemplateId || ""),
|
|
requestTemplateQuery: String(((_b2 = data == null ? void 0 : data.template) == null ? void 0 : _b2.name) || templateNameHint || prev.requestTemplateQuery || ""),
|
|
templateStatus: ""
|
|
};
|
|
});
|
|
} catch (error) {
|
|
setDataRequestModal((prev) => ({ ...prev, loading: false, error: error.message || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0448\u0430\u0431\u043B\u043E\u043D" }));
|
|
}
|
|
};
|
|
const applySelectedRequestTemplate = async () => {
|
|
const selectedByQuery = findRequestTemplateByQuery(dataRequestModal.requestTemplateQuery);
|
|
const templateId = String((selectedByQuery == null ? void 0 : selectedByQuery.id) || dataRequestModal.selectedRequestTemplateId || "").trim();
|
|
return applyRequestTemplateById(templateId, (selectedByQuery == null ? void 0 : selectedByQuery.name) || "");
|
|
};
|
|
const refreshDataRequestCatalog = async () => {
|
|
if (typeof onLoadRequestDataTemplates !== "function") return null;
|
|
const data = await onLoadRequestDataTemplates();
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
templates: Array.isArray(data == null ? void 0 : data.rows) ? data.rows : [],
|
|
templateList: Array.isArray(data == null ? void 0 : data.templates) ? data.templates : [],
|
|
availableDocuments: Array.isArray(data == null ? void 0 : data.documents) ? data.documents : [],
|
|
selectedRequestTemplateId: prev.selectedRequestTemplateId && (Array.isArray(data == null ? void 0 : data.templates) ? data.templates : []).some((item) => String(item == null ? void 0 : item.id) === String(prev.selectedRequestTemplateId)) ? prev.selectedRequestTemplateId : ""
|
|
}));
|
|
return data;
|
|
};
|
|
const saveCurrentDataRequestTemplate = async () => {
|
|
if (typeof onSaveRequestDataTemplate !== "function") return;
|
|
const selectedFromQuery = findRequestTemplateByQuery(dataRequestModal.requestTemplateQuery);
|
|
const templateName = String(dataRequestModal.requestTemplateQuery || "").trim();
|
|
const rows = (dataRequestModal.rows || []).filter((row2) => String(row2.label || "").trim());
|
|
if (!templateName) {
|
|
setDataRequestModal((prev) => ({ ...prev, error: "\u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0448\u0430\u0431\u043B\u043E\u043D\u0430" }));
|
|
return;
|
|
}
|
|
if (!rows.length) {
|
|
setDataRequestModal((prev) => ({ ...prev, error: "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u043D\u043E \u043F\u043E\u043B\u0435 \u0434\u043B\u044F \u0448\u0430\u0431\u043B\u043E\u043D\u0430" }));
|
|
return;
|
|
}
|
|
setDataRequestModal((prev) => ({ ...prev, savingTemplate: true, error: "", templateStatus: "" }));
|
|
try {
|
|
const result = await onSaveRequestDataTemplate({
|
|
template_id: String((selectedFromQuery == null ? void 0 : selectedFromQuery.id) || dataRequestModal.selectedRequestTemplateId || "").trim() || void 0,
|
|
name: templateName,
|
|
items: rows.map((row2) => ({
|
|
topic_data_template_id: row2.topic_template_id || void 0,
|
|
key: row2.key || void 0,
|
|
label: row2.label,
|
|
value_type: row2.field_type || "string"
|
|
}))
|
|
});
|
|
const savedRows = (Array.isArray(result == null ? void 0 : result.items) ? result.items : []).map(
|
|
(item) => newDataRequestRow({
|
|
...item,
|
|
topic_template_id: item.topic_data_template_id || item.topic_template_id || "",
|
|
field_type: item.value_type || item.field_type
|
|
})
|
|
);
|
|
setDataRequestModal((prev) => {
|
|
var _a2, _b2;
|
|
return {
|
|
...prev,
|
|
savingTemplate: false,
|
|
rows: savedRows.length ? savedRows : prev.rows,
|
|
selectedRequestTemplateId: String(((_a2 = result == null ? void 0 : result.template) == null ? void 0 : _a2.id) || prev.selectedRequestTemplateId || ""),
|
|
requestTemplateQuery: String(((_b2 = result == null ? void 0 : result.template) == null ? void 0 : _b2.name) || templateName),
|
|
templateStatus: "\u0428\u0430\u0431\u043B\u043E\u043D \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D"
|
|
};
|
|
});
|
|
await refreshDataRequestCatalog();
|
|
} catch (error) {
|
|
setDataRequestModal((prev) => ({ ...prev, savingTemplate: false, error: error.message || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0448\u0430\u0431\u043B\u043E\u043D" }));
|
|
}
|
|
};
|
|
const addSelectedTemplateRow = () => {
|
|
const selectedByQuery = findCatalogFieldByQuery(dataRequestModal.catalogFieldQuery);
|
|
const templateId = String((selectedByQuery == null ? void 0 : selectedByQuery.id) || dataRequestModal.selectedCatalogTemplateId || "").trim();
|
|
const template = (dataRequestModal.templates || []).find((item) => String(item.id) === templateId);
|
|
if (!template) {
|
|
const manualLabel = String(dataRequestModal.catalogFieldQuery || "").trim();
|
|
if (!manualLabel) return;
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
catalogFieldQuery: "",
|
|
templateStatus: "",
|
|
rows: [...prev.rows || [], newDataRequestRow({ label: manualLabel, field_type: "string" })]
|
|
}));
|
|
return;
|
|
}
|
|
setDataRequestModal((prev) => {
|
|
const exists = (prev.rows || []).some((row2) => String(row2.key || "") === String(template.key || ""));
|
|
if (exists) return { ...prev, selectedCatalogTemplateId: "", catalogFieldQuery: "" };
|
|
return {
|
|
...prev,
|
|
selectedCatalogTemplateId: "",
|
|
catalogFieldQuery: "",
|
|
templateStatus: "",
|
|
rows: [...prev.rows || [], newDataRequestRow({ ...template, topic_template_id: template.id, field_type: template.value_type })]
|
|
};
|
|
});
|
|
};
|
|
const updateDataRequestRow = (localId, patch) => {
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
templateStatus: "",
|
|
rows: (prev.rows || []).map((row2) => row2.localId === localId ? { ...row2, ...patch || {} } : row2)
|
|
}));
|
|
};
|
|
const removeDataRequestRow = (localId) => {
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
templateStatus: "",
|
|
rows: (prev.rows || []).filter((row2) => row2.localId !== localId)
|
|
}));
|
|
};
|
|
const moveDataRequestRow = (localId, delta) => {
|
|
const shift = Number(delta) || 0;
|
|
if (!shift) return;
|
|
setDataRequestModal((prev) => {
|
|
const rows = Array.isArray(prev.rows) ? [...prev.rows] : [];
|
|
const index = rows.findIndex((row2) => row2.localId === localId);
|
|
if (index < 0) return prev;
|
|
const nextIndex = index + shift;
|
|
if (nextIndex < 0 || nextIndex >= rows.length) return prev;
|
|
const [item] = rows.splice(index, 1);
|
|
rows.splice(nextIndex, 0, item);
|
|
return { ...prev, templateStatus: "", rows };
|
|
});
|
|
};
|
|
const moveDataRequestRowToIndex = (localId, targetIndexRaw) => {
|
|
const targetIndex = Number(targetIndexRaw);
|
|
if (!Number.isInteger(targetIndex)) return;
|
|
setDataRequestModal((prev) => {
|
|
const rows = Array.isArray(prev.rows) ? [...prev.rows] : [];
|
|
const fromIndex = rows.findIndex((rowItem) => rowItem.localId === localId);
|
|
if (fromIndex < 0) return prev;
|
|
const boundedIndex = Math.max(0, Math.min(rows.length - 1, targetIndex));
|
|
if (fromIndex === boundedIndex) return prev;
|
|
const [item] = rows.splice(fromIndex, 1);
|
|
rows.splice(boundedIndex, 0, item);
|
|
return { ...prev, templateStatus: "", rows };
|
|
});
|
|
};
|
|
const submitDataRequestModal = async () => {
|
|
if (typeof onSaveRequestDataBatch !== "function") return;
|
|
const rows = (dataRequestModal.rows || []).filter((row2) => String(row2.label || "").trim());
|
|
if (!rows.length) {
|
|
setDataRequestModal((prev) => ({ ...prev, error: "\u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u043D\u043E \u043F\u043E\u043B\u0435" }));
|
|
return;
|
|
}
|
|
setDataRequestModal((prev) => ({ ...prev, saving: true, error: "" }));
|
|
try {
|
|
await onSaveRequestDataBatch({
|
|
message_id: dataRequestModal.messageId || void 0,
|
|
items: rows.map((row2) => ({
|
|
id: row2.id || void 0,
|
|
topic_template_id: row2.topic_template_id || void 0,
|
|
key: row2.key || void 0,
|
|
label: row2.label,
|
|
field_type: row2.field_type || "string",
|
|
document_name: row2.document_name || void 0
|
|
}))
|
|
});
|
|
closeDataRequestModal();
|
|
} catch (error) {
|
|
setDataRequestModal((prev) => ({ ...prev, saving: false, error: error.message || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0437\u0430\u043F\u0440\u043E\u0441" }));
|
|
}
|
|
};
|
|
const closeClientDataModal = () => {
|
|
setClientDataModal({
|
|
open: false,
|
|
loading: false,
|
|
saving: false,
|
|
messageId: "",
|
|
items: [],
|
|
status: "",
|
|
error: ""
|
|
});
|
|
};
|
|
const openClientDataRequestModal = async (messageId) => {
|
|
if (!canFillRequestData || typeof onLoadRequestDataBatch !== "function" || !messageId) return;
|
|
setClientDataModal({
|
|
open: true,
|
|
loading: true,
|
|
saving: false,
|
|
messageId: String(messageId),
|
|
items: [],
|
|
status: "",
|
|
error: ""
|
|
});
|
|
try {
|
|
const data = await onLoadRequestDataBatch(String(messageId));
|
|
const items = Array.isArray(data == null ? void 0 : data.items) ? data.items.slice().sort((a, b) => Number((a == null ? void 0 : a.sort_order) || 0) - Number((b == null ? void 0 : b.sort_order) || 0)).map((item, index) => ({
|
|
localId: "client-data-" + String((item == null ? void 0 : item.id) || (item == null ? void 0 : item.key) || index),
|
|
id: String((item == null ? void 0 : item.id) || ""),
|
|
key: String((item == null ? void 0 : item.key) || ""),
|
|
label: String((item == null ? void 0 : item.label) || (item == null ? void 0 : item.key) || "\u041F\u043E\u043B\u0435"),
|
|
field_type: String((item == null ? void 0 : item.field_type) || "string").toLowerCase(),
|
|
value_text: (item == null ? void 0 : item.value_text) == null ? "" : String(item.value_text),
|
|
value_file: (item == null ? void 0 : item.value_file) || null,
|
|
pendingFile: null
|
|
})) : [];
|
|
setClientDataModal((prev) => ({ ...prev, loading: false, items }));
|
|
} catch (error) {
|
|
setClientDataModal((prev) => ({ ...prev, loading: false, error: (error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u0430\u043D\u043D\u044B\u0445" }));
|
|
}
|
|
};
|
|
const updateClientDataItem = (localId, patch) => {
|
|
setClientDataModal((prev) => ({
|
|
...prev,
|
|
status: "",
|
|
error: "",
|
|
items: (prev.items || []).map((item) => item.localId === localId ? { ...item, ...patch || {} } : item)
|
|
}));
|
|
};
|
|
const submitClientDataModal = async (event) => {
|
|
if (event && typeof event.preventDefault === "function") event.preventDefault();
|
|
if (!canFillRequestData || typeof onSaveRequestDataValues !== "function") return;
|
|
const currentMessageId = String(clientDataModal.messageId || "").trim();
|
|
if (!currentMessageId) return;
|
|
setClientDataModal((prev) => ({ ...prev, saving: true, status: "", error: "" }));
|
|
try {
|
|
const payloadItems = [];
|
|
for (const item of clientDataModal.items || []) {
|
|
const fieldType = String((item == null ? void 0 : item.field_type) || "string").toLowerCase();
|
|
if (fieldType === "file") {
|
|
let attachmentId = String((item == null ? void 0 : item.value_text) || "").trim();
|
|
if (item == null ? void 0 : item.pendingFile) {
|
|
if (typeof onUploadRequestAttachment !== "function") {
|
|
throw new Error("\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0444\u0430\u0439\u043B\u0430 \u0434\u043B\u044F \u043F\u043E\u043B\u044F \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430");
|
|
}
|
|
const uploadResult = await onUploadRequestAttachment(item.pendingFile, {
|
|
source: "data_request",
|
|
message_id: currentMessageId,
|
|
key: String((item == null ? void 0 : item.key) || "")
|
|
});
|
|
attachmentId = String(
|
|
uploadResult && (uploadResult.attachment_id || uploadResult.id || uploadResult.value || uploadResult) || ""
|
|
).trim();
|
|
if (!attachmentId) throw new Error("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0434\u043B\u044F \u043F\u043E\u043B\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430");
|
|
}
|
|
payloadItems.push({
|
|
id: String((item == null ? void 0 : item.id) || ""),
|
|
key: String((item == null ? void 0 : item.key) || ""),
|
|
attachment_id: attachmentId || "",
|
|
value_text: attachmentId || ""
|
|
});
|
|
continue;
|
|
}
|
|
payloadItems.push({
|
|
id: String((item == null ? void 0 : item.id) || ""),
|
|
key: String((item == null ? void 0 : item.key) || ""),
|
|
value_text: String((item == null ? void 0 : item.value_text) || "")
|
|
});
|
|
}
|
|
await onSaveRequestDataValues({
|
|
message_id: currentMessageId,
|
|
items: payloadItems
|
|
});
|
|
closeClientDataModal();
|
|
} catch (error) {
|
|
setClientDataModal((prev) => ({
|
|
...prev,
|
|
saving: false,
|
|
error: (error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435"
|
|
}));
|
|
}
|
|
};
|
|
const handleRequestRowDragStart = (event, rowItem, rowLocked) => {
|
|
if (rowLocked || dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
setDraggedRequestRowId(String(rowItem.localId || ""));
|
|
setDragOverRequestRowId(String(rowItem.localId || ""));
|
|
try {
|
|
event.dataTransfer.effectAllowed = "move";
|
|
event.dataTransfer.setData("text/plain", String(rowItem.localId || ""));
|
|
} catch (_error) {
|
|
}
|
|
};
|
|
const handleRequestRowDragEnd = () => {
|
|
setDraggedRequestRowId("");
|
|
setDragOverRequestRowId("");
|
|
};
|
|
const appendStatusChangeFiles = (files) => {
|
|
const list = Array.isArray(files) ? files.filter(Boolean) : [];
|
|
if (!list.length) return;
|
|
setStatusChangeModal((prev) => {
|
|
const existing = Array.isArray(prev.files) ? prev.files : [];
|
|
const next = [...existing];
|
|
list.forEach((file) => {
|
|
const duplicate = next.some(
|
|
(item) => item && item.name === file.name && Number(item.size || 0) === Number(file.size || 0) && Number(item.lastModified || 0) === Number(file.lastModified || 0)
|
|
);
|
|
if (!duplicate) next.push(file);
|
|
});
|
|
return { ...prev, files: next };
|
|
});
|
|
};
|
|
const removeStatusChangeFile = (index) => {
|
|
setStatusChangeModal((prev) => {
|
|
const files = Array.isArray(prev.files) ? [...prev.files] : [];
|
|
files.splice(index, 1);
|
|
return { ...prev, files };
|
|
});
|
|
};
|
|
const submitStatusChange = async (event) => {
|
|
if (event && typeof event.preventDefault === "function") event.preventDefault();
|
|
if (!(row == null ? void 0 : row.id) || typeof onChangeStatus !== "function") return;
|
|
const nextStatus = String(statusChangeModal.statusCode || "").trim();
|
|
if (!nextStatus) {
|
|
setStatusChangeModal((prev) => ({ ...prev, error: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043D\u043E\u0432\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441" }));
|
|
return;
|
|
}
|
|
if (nextStatus === String((row == null ? void 0 : row.status_code) || "").trim()) {
|
|
setStatusChangeModal((prev) => ({ ...prev, error: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0430\u0442\u0443\u0441, \u043E\u0442\u043B\u0438\u0447\u043D\u044B\u0439 \u043E\u0442 \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E" }));
|
|
return;
|
|
}
|
|
setStatusChangeModal((prev) => ({ ...prev, saving: true, error: "" }));
|
|
try {
|
|
const localValue = String(statusChangeModal.importantDateAt || "").trim();
|
|
const importantDateIso = localValue ? new Date(localValue).toISOString() : "";
|
|
await onChangeStatus({
|
|
requestId: String(row.id),
|
|
statusCode: nextStatus,
|
|
importantDateAt: importantDateIso || null,
|
|
comment: statusChangeModal.comment || "",
|
|
files: statusChangeModal.files || []
|
|
});
|
|
closeStatusChangeModal();
|
|
} catch (error) {
|
|
setStatusChangeModal((prev) => ({ ...prev, saving: false, error: error.message || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u0430\u0442\u0443\u0441" }));
|
|
}
|
|
};
|
|
const chatTimelineItems = [];
|
|
let previousDate = "";
|
|
const timelineSource = [];
|
|
safeMessages.forEach((item) => {
|
|
timelineSource.push({
|
|
type: "message",
|
|
key: "msg-" + String((item == null ? void 0 : item.id) || Math.random()),
|
|
created_at: (item == null ? void 0 : item.created_at) || null,
|
|
payload: item
|
|
});
|
|
});
|
|
safeAttachments.filter((item) => !String((item == null ? void 0 : item.message_id) || "").trim()).forEach((item) => {
|
|
timelineSource.push({
|
|
type: "file",
|
|
key: "file-" + String((item == null ? void 0 : item.id) || Math.random()),
|
|
created_at: (item == null ? void 0 : item.created_at) || null,
|
|
payload: item
|
|
});
|
|
});
|
|
timelineSource.sort((a, b) => {
|
|
const aTime = new Date(a.created_at || 0).getTime();
|
|
const bTime = new Date(b.created_at || 0).getTime();
|
|
if (!Number.isFinite(aTime) && !Number.isFinite(bTime)) return 0;
|
|
if (!Number.isFinite(aTime)) return 1;
|
|
if (!Number.isFinite(bTime)) return -1;
|
|
if (aTime !== bTime) return aTime - bTime;
|
|
return String(a.key).localeCompare(String(b.key), "ru");
|
|
});
|
|
timelineSource.forEach((entry, index) => {
|
|
const dateLabel = fmtDateOnly(entry.created_at);
|
|
const normalizedDate = dateLabel && dateLabel !== "-" ? dateLabel : "\u0411\u0435\u0437 \u0434\u0430\u0442\u044B";
|
|
if (normalizedDate !== previousDate) {
|
|
chatTimelineItems.push({ type: "date", key: "date-" + normalizedDate + "-" + index, label: normalizedDate });
|
|
previousDate = normalizedDate;
|
|
}
|
|
chatTimelineItems.push(entry);
|
|
});
|
|
useEffect(() => {
|
|
if (chatTab !== "chat") return;
|
|
const listNode = chatListRef.current;
|
|
if (!listNode) return;
|
|
const cursor = String(localActivityCursor || "");
|
|
if (!cursor || cursor === lastAutoScrollCursorRef.current) return;
|
|
lastAutoScrollCursorRef.current = cursor;
|
|
const raf = window.requestAnimationFrame(() => {
|
|
if (!chatListRef.current) return;
|
|
chatListRef.current.scrollTop = chatListRef.current.scrollHeight;
|
|
});
|
|
return () => window.cancelAnimationFrame(raf);
|
|
}, [chatTab, localActivityCursor]);
|
|
const baseRouteNodes = Array.isArray(statusRouteNodes) && statusRouteNodes.length ? statusRouteNodes : (row == null ? void 0 : row.status_code) ? [{ code: row.status_code, name: String((row == null ? void 0 : row.status_name) || statusLabel(row.status_code) || row.status_code), state: "current", note: "\u0422\u0435\u043A\u0443\u0449\u0438\u0439 \u044D\u0442\u0430\u043F \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0438 \u0437\u0430\u044F\u0432\u043A\u0438" }] : [];
|
|
const upcomingImportantDate = useMemo(() => {
|
|
const source = String(currentImportantDateAt || (row == null ? void 0 : row.important_date_at) || "").trim();
|
|
if (!source) return "";
|
|
const timestamp = new Date(source).getTime();
|
|
if (!Number.isFinite(timestamp) || timestamp <= Date.now()) return "";
|
|
return new Date(timestamp).toISOString();
|
|
}, [currentImportantDateAt, row == null ? void 0 : row.important_date_at]);
|
|
const routeNodes = useMemo(() => {
|
|
if (viewerRoleCode !== "CLIENT" && viewerRoleCode !== "LAWYER" || !upcomingImportantDate) return baseRouteNodes;
|
|
if (!Array.isArray(baseRouteNodes) || !baseRouteNodes.length) {
|
|
return [
|
|
{
|
|
code: "__IMPORTANT_DATE__",
|
|
name: "\u0412\u0430\u0436\u043D\u0430\u044F \u0434\u0430\u0442\u0430",
|
|
state: "pending",
|
|
changed_at: upcomingImportantDate,
|
|
note: "\u041A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u044B\u0439 \u0441\u0440\u043E\u043A"
|
|
}
|
|
];
|
|
}
|
|
const hasVirtualNode = baseRouteNodes.some((node) => String((node == null ? void 0 : node.code) || "").trim() === "__IMPORTANT_DATE__");
|
|
if (hasVirtualNode) return baseRouteNodes;
|
|
const currentIndex = baseRouteNodes.findIndex((node) => String((node == null ? void 0 : node.state) || "").trim().toLowerCase() === "current");
|
|
const virtualNode = {
|
|
code: "__IMPORTANT_DATE__",
|
|
name: "\u0412\u0430\u0436\u043D\u0430\u044F \u0434\u0430\u0442\u0430",
|
|
state: "pending",
|
|
changed_at: upcomingImportantDate,
|
|
note: "\u041A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u044B\u0439 \u0441\u0440\u043E\u043A"
|
|
};
|
|
if (currentIndex < 0) return [...baseRouteNodes, virtualNode];
|
|
const next = [...baseRouteNodes];
|
|
next.splice(currentIndex + 1, 0, virtualNode);
|
|
return next;
|
|
}, [baseRouteNodes, upcomingImportantDate, viewerRoleCode]);
|
|
const routeNodesForDisplay = useMemo(() => {
|
|
if (!Array.isArray(routeNodes) || !routeNodes.length) return [];
|
|
const important = [];
|
|
const current = [];
|
|
const completed = [];
|
|
const pending = [];
|
|
routeNodes.forEach((node) => {
|
|
const code = String((node == null ? void 0 : node.code) || "").trim();
|
|
const state = String((node == null ? void 0 : node.state) || "pending").trim().toLowerCase();
|
|
if (code === "__IMPORTANT_DATE__") {
|
|
important.push(node);
|
|
return;
|
|
}
|
|
if (state === "current") {
|
|
current.push(node);
|
|
return;
|
|
}
|
|
if (state === "completed") {
|
|
completed.push(node);
|
|
return;
|
|
}
|
|
pending.push(node);
|
|
});
|
|
return [...important, ...current, ...completed.reverse(), ...pending];
|
|
}, [routeNodes]);
|
|
const AttachmentPreviewModal = AttachmentPreviewModalComponent;
|
|
const StatusLine = StatusLineComponent;
|
|
const resolveMessageReceiptState = (payload) => {
|
|
const authorType = String((payload == null ? void 0 : payload.author_type) || "").trim().toUpperCase();
|
|
const isClientAuthor = authorType === "CLIENT";
|
|
const deliveredAt = isClientAuthor ? payload == null ? void 0 : payload.delivered_to_staff_at : payload == null ? void 0 : payload.delivered_to_client_at;
|
|
const readAt = isClientAuthor ? payload == null ? void 0 : payload.read_by_staff_at : payload == null ? void 0 : payload.read_by_client_at;
|
|
if (readAt) return { state: "read", label: "\u041F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043E" };
|
|
if (deliveredAt) return { state: "delivered", label: "\u0414\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E" };
|
|
return { state: "sent", label: "\u041E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E" };
|
|
};
|
|
const isOutgoingForViewer = (payload) => {
|
|
const authorType = String((payload == null ? void 0 : payload.author_type) || "").trim().toUpperCase();
|
|
if (!authorType) return false;
|
|
if (viewerRoleCode === "CLIENT") return authorType === "CLIENT";
|
|
return authorType !== "CLIENT";
|
|
};
|
|
const renderMessageMeta = (payload) => {
|
|
const timeLabel = fmtTimeOnly(payload == null ? void 0 : payload.created_at);
|
|
if (!isOutgoingForViewer(payload)) return /* @__PURE__ */ React.createElement("div", { className: "chat-message-time" }, timeLabel);
|
|
const receipt = resolveMessageReceiptState(payload);
|
|
return /* @__PURE__ */ React.createElement("div", { className: "chat-message-meta" }, /* @__PURE__ */ React.createElement("div", { className: "chat-message-time" }, timeLabel), /* @__PURE__ */ React.createElement("span", { className: "chat-message-status " + receipt.state, title: receipt.label, "aria-label": receipt.label }, /* @__PURE__ */ React.createElement("span", { className: "chat-message-status-check first", "aria-hidden": "true" }, "\u2713"), receipt.state !== "sent" ? /* @__PURE__ */ React.createElement("span", { className: "chat-message-status-check second", "aria-hidden": "true" }, "\u2713") : null));
|
|
};
|
|
const renderRequestDataMessageItems = (payload) => {
|
|
var _a2;
|
|
const items = Array.isArray(payload == null ? void 0 : payload.request_data_items) ? payload.request_data_items : [];
|
|
const allFilled = Boolean(payload == null ? void 0 : payload.request_data_all_filled);
|
|
if (!items.length) return /* @__PURE__ */ React.createElement("p", { className: "chat-message-text" }, "\u0417\u0430\u043F\u0440\u043E\u0441");
|
|
if (allFilled) {
|
|
const fileOnly = items.length === 1 && String(((_a2 = items[0]) == null ? void 0 : _a2.field_type) || "").toLowerCase() === "file";
|
|
return /* @__PURE__ */ React.createElement("p", { className: "chat-message-text chat-request-data-collapsed" }, fileOnly ? "\u0424\u0430\u0439\u043B" : "\u0417\u0430\u043F\u043E\u043B\u043D\u0435\u043D");
|
|
}
|
|
const visibleItems = items.slice(0, 7);
|
|
const hiddenCount = Math.max(0, items.length - visibleItems.length);
|
|
return /* @__PURE__ */ React.createElement("div", { className: "chat-request-data-list" }, visibleItems.map((item, idx) => /* @__PURE__ */ React.createElement("div", { className: "chat-request-data-item" + ((item == null ? void 0 : item.is_filled) ? " filled" : ""), key: String((item == null ? void 0 : item.id) || idx) }, /* @__PURE__ */ React.createElement("span", { className: "chat-request-data-index" }, (item == null ? void 0 : item.is_filled) ? /* @__PURE__ */ React.createElement("span", { className: "chat-request-data-check" }, "\u2713") : null, String((item == null ? void 0 : item.index) || idx + 1) + "."), /* @__PURE__ */ React.createElement("span", { className: "chat-request-data-label" }, String((item == null ? void 0 : item.label_short) || (item == null ? void 0 : item.label) || "\u041F\u043E\u043B\u0435")))), hiddenCount > 0 ? /* @__PURE__ */ React.createElement("div", { className: "chat-request-data-more" }, "... \u0435\u0449\u0435 ", hiddenCount) : null);
|
|
};
|
|
const resolveServiceMessageContent = (payload) => {
|
|
const messageKind = String((payload == null ? void 0 : payload.message_kind) || "");
|
|
if (messageKind === "REQUEST_DATA") return null;
|
|
const bodyRaw = String((payload == null ? void 0 : payload.body) || "").replace(/\r/g, "").trim();
|
|
if (!bodyRaw) return null;
|
|
const lines = bodyRaw.split("\n");
|
|
const firstLine = String(lines[0] || "").trim();
|
|
const restLines = lines.slice(1);
|
|
const normalizeDetail = (value) => String(value || "").trim();
|
|
const withTail = (firstDetail) => [normalizeDetail(firstDetail), ...restLines.map((line) => normalizeDetail(line)).filter(Boolean)].filter(Boolean).join("\n");
|
|
if (firstLine === "\u0421\u0447\u0435\u0442 \u043D\u0430 \u043E\u043F\u043B\u0430\u0442\u0443" || firstLine.startsWith("\u0421\u0447\u0435\u0442 \u043D\u0430 \u043E\u043F\u043B\u0430\u0442\u0443:")) {
|
|
return {
|
|
title: "\u0421\u0447\u0435\u0442 \u043D\u0430 \u043E\u043F\u043B\u0430\u0442\u0443",
|
|
text: withTail(firstLine.startsWith("\u0421\u0447\u0435\u0442 \u043D\u0430 \u043E\u043F\u043B\u0430\u0442\u0443:") ? firstLine.slice("\u0421\u0447\u0435\u0442 \u043D\u0430 \u043E\u043F\u043B\u0430\u0442\u0443:".length) : "")
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u0418\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0441\u0442\u0430\u0442\u0443\u0441:") || firstLine.startsWith("\u0421\u043C\u0435\u043D\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430:")) {
|
|
const source = firstLine.startsWith("\u0418\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0441\u0442\u0430\u0442\u0443\u0441:") ? firstLine : firstLine.slice("\u0421\u043C\u0435\u043D\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430:".length);
|
|
const detail = firstLine.startsWith("\u0418\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0441\u0442\u0430\u0442\u0443\u0441:") ? source.slice("\u0418\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0441\u0442\u0430\u0442\u0443\u0441:".length) : source;
|
|
return {
|
|
title: "\u0418\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0441\u0442\u0430\u0442\u0443\u0441",
|
|
text: withTail(detail)
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D \u044E\u0440\u0438\u0441\u0442:")) {
|
|
return {
|
|
title: "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D \u044E\u0440\u0438\u0441\u0442",
|
|
text: withTail(firstLine.slice("\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D \u044E\u0440\u0438\u0441\u0442:".length))
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u041F\u0435\u0440\u0435\u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u043E:")) {
|
|
return {
|
|
title: "\u0421\u043C\u0435\u043D\u0430 \u044E\u0440\u0438\u0441\u0442\u0430",
|
|
text: withTail(firstLine.slice("\u041F\u0435\u0440\u0435\u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u043E:".length))
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u0421\u043C\u0435\u043D\u0430 \u044E\u0440\u0438\u0441\u0442\u0430:")) {
|
|
return {
|
|
title: "\u0421\u043C\u0435\u043D\u0430 \u044E\u0440\u0438\u0441\u0442\u0430",
|
|
text: withTail(firstLine.slice("\u0421\u043C\u0435\u043D\u0430 \u044E\u0440\u0438\u0441\u0442\u0430:".length))
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u044E\u0440\u0438\u0441\u0442\u0430:")) {
|
|
return {
|
|
title: "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D \u044E\u0440\u0438\u0441\u0442",
|
|
text: withTail(firstLine.slice("\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u044E\u0440\u0438\u0441\u0442\u0430:".length))
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435:")) {
|
|
return {
|
|
title: "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D \u044E\u0440\u0438\u0441\u0442",
|
|
text: withTail(firstLine.slice("\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435:".length))
|
|
};
|
|
}
|
|
if (firstLine.startsWith("\u041F\u0435\u0440\u0435\u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435:")) {
|
|
return {
|
|
title: "\u0421\u043C\u0435\u043D\u0430 \u044E\u0440\u0438\u0441\u0442\u0430",
|
|
text: withTail(firstLine.slice("\u041F\u0435\u0440\u0435\u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435:".length))
|
|
};
|
|
}
|
|
return null;
|
|
};
|
|
const resolveStatusDisplayName = (code, explicitName) => {
|
|
var _a2;
|
|
const explicit = String(explicitName || "").trim();
|
|
if (explicit) return explicit;
|
|
const normalizedCode = String(code || "").trim();
|
|
if (!normalizedCode) return "-";
|
|
const optionName = String(((_a2 = statusByCode.get(normalizedCode)) == null ? void 0 : _a2.name) || "").trim();
|
|
if (optionName) return optionName;
|
|
const legacyName = String(statusLabel(normalizedCode) || "").trim();
|
|
if (legacyName && legacyName !== normalizedCode) return legacyName;
|
|
return humanizeKey(normalizedCode);
|
|
};
|
|
const formatRequestDataValue = (item) => {
|
|
const type = String((item == null ? void 0 : item.field_type) || "string").toLowerCase();
|
|
if (type === "date") {
|
|
const text2 = String((item == null ? void 0 : item.value_text) || "").trim();
|
|
return text2 ? fmtDateOnly(text2) : "\u041D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E";
|
|
}
|
|
if (type === "file") {
|
|
const attachmentId = String((item == null ? void 0 : item.value_text) || "").trim();
|
|
const linkedAttachment = attachmentId ? attachmentById.get(attachmentId) : null;
|
|
const fileMeta = (item == null ? void 0 : item.value_file) || (linkedAttachment ? {
|
|
attachment_id: linkedAttachment.id,
|
|
file_name: linkedAttachment.file_name,
|
|
mime_type: linkedAttachment.mime_type,
|
|
size_bytes: linkedAttachment.size_bytes,
|
|
download_url: linkedAttachment.download_url
|
|
} : null);
|
|
return fileMeta || null;
|
|
}
|
|
const text = String((item == null ? void 0 : item.value_text) || "").trim();
|
|
return text || "\u041D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E";
|
|
};
|
|
const currentStatusName = resolveStatusDisplayName(row == null ? void 0 : row.status_code, (row == null ? void 0 : row.status_name) || "");
|
|
const dataRequestProgress = useMemo(() => {
|
|
const rows = Array.isArray(dataRequestModal.rows) ? dataRequestModal.rows : [];
|
|
const total = rows.length;
|
|
const filled = rows.filter((rowItem) => Boolean((rowItem == null ? void 0 : rowItem.is_filled) || String((rowItem == null ? void 0 : rowItem.value_text) || "").trim())).length;
|
|
return { total, filled };
|
|
}, [dataRequestModal.rows]);
|
|
return /* @__PURE__ */ React.createElement("div", { className: "block" }, /* @__PURE__ */ React.createElement("div", { className: "request-workspace-layout" }, /* @__PURE__ */ React.createElement("div", { className: "request-main-column" }, /* @__PURE__ */ React.createElement("div", { className: "block" }, /* @__PURE__ */ React.createElement("div", { className: "request-card-head" }, /* @__PURE__ */ React.createElement("h3", null, "\u041A\u0430\u0440\u0442\u043E\u0447\u043A\u0430"), /* @__PURE__ */ React.createElement("div", { className: "request-card-head-actions" }, canRequestData ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn request-card-status-btn",
|
|
"data-tooltip": "\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u0430\u0442\u0443\u0441",
|
|
"aria-label": "\u0421\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0442\u0430\u0442\u0443\u0441",
|
|
onClick: () => openStatusChangeModal(),
|
|
disabled: loading || !row
|
|
},
|
|
"\u21C4"
|
|
) : null, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn request-card-data-btn",
|
|
"data-tooltip": "\u0414\u0430\u043D\u043D\u044B\u0435 \u0437\u0430\u044F\u0432\u043A\u0438",
|
|
"aria-label": "\u0414\u0430\u043D\u043D\u044B\u0435 \u0437\u0430\u044F\u0432\u043A\u0438",
|
|
onClick: () => setRequestDataListOpen(true),
|
|
disabled: loading || !row
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement("path", { d: "M4 5h16v2H4V5Zm0 6h16v2H4v-2Zm0 6h10v2H4v-2Z", fill: "currentColor" }))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn request-card-finance-btn",
|
|
"data-tooltip": "\u0424\u0438\u043D\u0430\u043D\u0441\u044B \u0437\u0430\u044F\u0432\u043A\u0438",
|
|
"aria-label": "\u0424\u0438\u043D\u0430\u043D\u0441\u044B \u0437\u0430\u044F\u0432\u043A\u0438",
|
|
onClick: () => setFinanceOpen(true),
|
|
disabled: loading || !row
|
|
},
|
|
"$"
|
|
))), /* @__PURE__ */ React.createElement("div", { className: "request-card-head-spacer", "aria-hidden": "true" }), loading ? /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...") : row ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "request-card-grid request-card-grid-compact" }, showTopicStatusInCard ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0422\u0435\u043C\u0430"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, String(row.topic_name || row.topic_code || "-"))), /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0421\u0442\u0430\u0442\u0443\u0441"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, currentStatusName))) : null, /* @__PURE__ */ React.createElement("div", { className: "request-field request-field-span-2 request-field-description" }, /* @__PURE__ */ React.createElement("div", { className: "request-field-head" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B"), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn request-field-expand-btn",
|
|
"data-tooltip": "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435",
|
|
"aria-label": "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435",
|
|
onClick: () => setDescriptionOpen(true)
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M4 9V4h5v2H6v3H4zm10-5h6v6h-2V6h-4V4zM4 15h2v3h3v2H4v-5zm14 3v-3h2v5h-5v-2h3z",
|
|
fill: "currentColor"
|
|
}
|
|
))
|
|
)), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, row.description ? String(row.description) : "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E")), showContactsInCard ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u041A\u043B\u0438\u0435\u043D\u0442"), /* @__PURE__ */ React.createElement(
|
|
"span",
|
|
{
|
|
className: "request-field-value" + (clientHasPhone ? " has-tooltip request-contact-value" : ""),
|
|
"data-tooltip": clientHasPhone ? clientPhone : void 0
|
|
},
|
|
clientLabel
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u042E\u0440\u0438\u0441\u0442"), /* @__PURE__ */ React.createElement(
|
|
"span",
|
|
{
|
|
className: "request-field-value" + (lawyerHasPhone ? " has-tooltip request-contact-value" : ""),
|
|
"data-tooltip": lawyerHasPhone ? lawyerPhone : void 0
|
|
},
|
|
lawyerLabel
|
|
))) : null, canSeeCreatedUpdatedInCard ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0421\u043E\u0437\u0434\u0430\u043D\u0430"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtShortDateTime(row.created_at))), /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0430"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtShortDateTime(row.updated_at)))) : null), /* @__PURE__ */ React.createElement("div", { className: "request-status-route" }, /* @__PURE__ */ React.createElement("h4", null, "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u0432"), routeNodesForDisplay.length ? /* @__PURE__ */ React.createElement("ol", { className: "request-route-list", id: "request-status-route" }, routeNodesForDisplay.map((node, index) => {
|
|
const state = String((node == null ? void 0 : node.state) || "pending");
|
|
const code = String((node == null ? void 0 : node.code) || "").trim();
|
|
const rawName = String((node == null ? void 0 : node.name) || "").trim();
|
|
const name = resolveStatusDisplayName(code, rawName && rawName !== code ? rawName : "");
|
|
const note = String((node == null ? void 0 : node.note) || "").trim();
|
|
const isImportantDateNode = code === "__IMPORTANT_DATE__";
|
|
const changedAtSource = String((node == null ? void 0 : node.changed_at) || "").trim() || (isImportantDateNode ? String(currentImportantDateAt || (row == null ? void 0 : row.important_date_at) || "").trim() : "");
|
|
const changedAt = changedAtSource ? fmtDate(changedAtSource) : "";
|
|
const className = "route-item " + (state === "current" ? "current" : state === "completed" ? "completed" : "pending") + (isImportantDateNode ? " important-date" : "");
|
|
return /* @__PURE__ */ React.createElement("li", { className, key: ((node == null ? void 0 : node.code) || "node") + "-" + index }, /* @__PURE__ */ React.createElement("span", { className: "route-dot" }), /* @__PURE__ */ React.createElement("div", { className: "route-body" }, /* @__PURE__ */ React.createElement("b", null, name), isImportantDateNode ? /* @__PURE__ */ React.createElement("p", null, "\u041A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u044B\u0439 \u0441\u0440\u043E\u043A: " + (changedAt || "-")) : /* @__PURE__ */ React.createElement(React.Fragment, null, note ? /* @__PURE__ */ React.createElement("p", null, note) : null, /* @__PURE__ */ React.createElement("div", { className: "muted route-time" }, "\u0414\u0430\u0442\u0430 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F: ", changedAt || "-"))));
|
|
})) : /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u041C\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u0432 \u0434\u043B\u044F \u0442\u0435\u043C\u044B \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D"))) : /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u041D\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u043F\u043E \u0437\u0430\u044F\u0432\u043A\u0435"))), /* @__PURE__ */ React.createElement("div", { className: "block request-chat-block" }, /* @__PURE__ */ React.createElement("div", { className: "request-chat-head" }, /* @__PURE__ */ React.createElement("h3", null, "\u041A\u043E\u043C\u043C\u0443\u043D\u0438\u043A\u0430\u0446\u0438\u044F"), /* @__PURE__ */ React.createElement("div", { className: "request-chat-tabs", role: "tablist", "aria-label": "\u041A\u043E\u043C\u043C\u0443\u043D\u0438\u043A\u0430\u0446\u0438\u044F" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
role: "tab",
|
|
"aria-selected": chatTab === "chat",
|
|
className: "tab-btn" + (chatTab === "chat" ? " active" : ""),
|
|
onClick: () => setChatTab("chat")
|
|
},
|
|
"\u0427\u0430\u0442"
|
|
), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
role: "tab",
|
|
"aria-selected": chatTab === "files",
|
|
className: "tab-btn" + (chatTab === "files" ? " active" : ""),
|
|
onClick: () => setChatTab("files")
|
|
},
|
|
"\u0424\u0430\u0439\u043B\u044B" + (safeAttachments.length ? " (" + safeAttachments.length + ")" : "")
|
|
))), /* @__PURE__ */ React.createElement("div", { className: "request-chat-live-row", "aria-live": "polite" }, /* @__PURE__ */ React.createElement("span", { className: "chat-live-dot" + (liveMode === "degraded" ? " degraded" : "") }), /* @__PURE__ */ React.createElement("span", { className: "request-chat-live-text" }, typingHintText || (liveMode === "degraded" ? "\u0421\u0432\u044F\u0437\u044C \u043D\u0435\u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u0430, \u0432\u043A\u043B\u044E\u0447\u0435\u043D backoff" : "\u041E\u043D\u043B\u0430\u0439\u043D"))), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: idMap.fileInput,
|
|
ref: fileInputRef,
|
|
type: "file",
|
|
multiple: true,
|
|
onChange: onInputFiles,
|
|
disabled: loading || fileUploading,
|
|
style: { position: "absolute", width: "1px", height: "1px", opacity: 0, pointerEvents: "none" }
|
|
}
|
|
), chatTab === "chat" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("ul", { className: "simple-list request-modal-list request-chat-list", id: idMap.messagesList, ref: chatListRef }, chatTimelineItems.length ? chatTimelineItems.map(
|
|
(entry) => {
|
|
var _a2, _b2, _c2, _d, _e;
|
|
return entry.type === "date" ? /* @__PURE__ */ React.createElement("li", { key: entry.key, className: "chat-date-divider" }, /* @__PURE__ */ React.createElement("span", null, entry.label)) : entry.type === "file" ? /* @__PURE__ */ React.createElement(
|
|
"li",
|
|
{
|
|
key: entry.key,
|
|
className: "chat-message " + (String(((_a2 = entry.payload) == null ? void 0 : _a2.responsible) || "").toUpperCase().includes("\u041A\u041B\u0418\u0415\u041D\u0422") ? "incoming" : "outgoing")
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "chat-message-author" }, String(((_b2 = entry.payload) == null ? void 0 : _b2.responsible) || "\u0421\u0438\u0441\u0442\u0435\u043C\u0430")),
|
|
/* @__PURE__ */ React.createElement("div", { className: "chat-message-bubble" }, /* @__PURE__ */ React.createElement("div", { className: "chat-message-files" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "chat-message-file-chip",
|
|
onClick: () => openAttachmentFromMessage(entry.payload),
|
|
title: String(((_c2 = entry.payload) == null ? void 0 : _c2.file_name) || "\u0424\u0430\u0439\u043B")
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"),
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-name" }, String(((_d = entry.payload) == null ? void 0 : _d.file_name) || "\u0424\u0430\u0439\u043B"))
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "chat-message-time" }, fmtTimeOnly((_e = entry.payload) == null ? void 0 : _e.created_at)))
|
|
) : (() => {
|
|
var _a3, _b3, _c3, _d2, _e2, _f, _g, _h;
|
|
const messageKind = String(((_a3 = entry.payload) == null ? void 0 : _a3.message_kind) || "");
|
|
const isRequestDataMessage = messageKind === "REQUEST_DATA";
|
|
const serviceMessageContent = resolveServiceMessageContent(entry.payload);
|
|
const requestDataInteractive = isRequestDataMessage && (canRequestData || canFillRequestData);
|
|
const bubbleClass = "chat-message-bubble" + (isRequestDataMessage ? " chat-request-data-bubble" : "") + (((_b3 = entry.payload) == null ? void 0 : _b3.request_data_all_filled) ? " all-filled" : "") + (isRequestDataMessage && canFillRequestData ? " request-data-message-btn" : "");
|
|
const itemClass = "chat-message " + (String(((_c3 = entry.payload) == null ? void 0 : _c3.author_type) || "").toUpperCase() === "CLIENT" ? "incoming" : "outgoing") + (isRequestDataMessage && canFillRequestData ? " request-data-item" + (((_d2 = entry.payload) == null ? void 0 : _d2.request_data_all_filled) ? " done" : "") : "");
|
|
return /* @__PURE__ */ React.createElement("li", { key: entry.key, className: itemClass }, /* @__PURE__ */ React.createElement("div", { className: "chat-message-author" }, String(((_e2 = entry.payload) == null ? void 0 : _e2.author_name) || ((_f = entry.payload) == null ? void 0 : _f.author_type) || "\u0421\u0438\u0441\u0442\u0435\u043C\u0430")), /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: bubbleClass,
|
|
onClick: requestDataInteractive ? () => {
|
|
var _a4, _b4;
|
|
return canRequestData ? openEditDataRequestModal(String(((_a4 = entry.payload) == null ? void 0 : _a4.id) || "")) : openClientDataRequestModal(String(((_b4 = entry.payload) == null ? void 0 : _b4.id) || ""));
|
|
} : void 0,
|
|
role: requestDataInteractive ? "button" : void 0,
|
|
tabIndex: requestDataInteractive ? 0 : void 0,
|
|
onKeyDown: requestDataInteractive ? (event) => {
|
|
var _a4, _b4;
|
|
if (event.key === "Enter" || event.key === " ") {
|
|
event.preventDefault();
|
|
if (canRequestData) openEditDataRequestModal(String(((_a4 = entry.payload) == null ? void 0 : _a4.id) || ""));
|
|
else openClientDataRequestModal(String(((_b4 = entry.payload) == null ? void 0 : _b4.id) || ""));
|
|
}
|
|
} : void 0
|
|
},
|
|
String(((_g = entry.payload) == null ? void 0 : _g.message_kind) || "") === "REQUEST_DATA" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "chat-request-data-head" }, "\u0417\u0430\u043F\u0440\u043E\u0441"), renderRequestDataMessageItems(entry.payload)) : /* @__PURE__ */ React.createElement(React.Fragment, null, (serviceMessageContent == null ? void 0 : serviceMessageContent.title) ? /* @__PURE__ */ React.createElement("div", { className: "chat-service-head" }, serviceMessageContent.title) : null, serviceMessageContent ? serviceMessageContent.text ? /* @__PURE__ */ React.createElement("p", { className: "chat-message-text" }, serviceMessageContent.text) : null : /* @__PURE__ */ React.createElement("p", { className: "chat-message-text" }, String(((_h = entry.payload) == null ? void 0 : _h.body) || ""))),
|
|
(() => {
|
|
var _a4, _b4;
|
|
if (String(((_a4 = entry.payload) == null ? void 0 : _a4.message_kind) || "") === "REQUEST_DATA") return null;
|
|
const messageId = String(((_b4 = entry.payload) == null ? void 0 : _b4.id) || "").trim();
|
|
if (!messageId) return null;
|
|
const messageFiles = attachmentsByMessageId.get(messageId) || [];
|
|
if (!messageFiles.length) return null;
|
|
return /* @__PURE__ */ React.createElement("div", { className: "chat-message-files" }, messageFiles.map((file) => /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
key: String(file.id),
|
|
className: "chat-message-file-chip",
|
|
onClick: () => openAttachmentFromMessage(file),
|
|
title: String(file.file_name || "\u0424\u0430\u0439\u043B")
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"),
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-name" }, String(file.file_name || "\u0424\u0430\u0439\u043B"))
|
|
)));
|
|
})(),
|
|
renderMessageMeta(entry.payload)
|
|
));
|
|
})();
|
|
}
|
|
) : /* @__PURE__ */ React.createElement("li", { className: "muted chat-empty-state" }, "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u043D\u0435\u0442")), /* @__PURE__ */ React.createElement("form", { className: "stack", onSubmit: onSendMessage }, /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "field request-chat-composer-dropzone" + (dropActive ? " drag-active" : ""),
|
|
onDragOver: (event) => {
|
|
event.preventDefault();
|
|
setDropActive(true);
|
|
},
|
|
onDragLeave: (event) => {
|
|
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
setDropActive(false);
|
|
},
|
|
onDrop: onDropFiles
|
|
},
|
|
/* @__PURE__ */ React.createElement("label", { htmlFor: idMap.messageBody }, "\u041D\u043E\u0432\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435"),
|
|
/* @__PURE__ */ React.createElement(
|
|
"textarea",
|
|
{
|
|
id: idMap.messageBody,
|
|
placeholder: messagePlaceholder,
|
|
value: messageDraft,
|
|
onChange: onMessageChange,
|
|
onFocus: () => setComposerFocused(true),
|
|
onBlur: () => setComposerFocused(false),
|
|
disabled: loading || fileUploading
|
|
}
|
|
),
|
|
/* @__PURE__ */ React.createElement("div", { className: "request-drop-hint muted" }, "\u041F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B \u0441\u044E\u0434\u0430 \u0438\u043B\u0438 \u043F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u0435 \u0441\u043A\u0440\u0435\u043F\u043A\u043E\u0439")
|
|
), hasPendingFiles ? /* @__PURE__ */ React.createElement("div", { className: "request-pending-files" }, pendingFiles.map((file, index) => /* @__PURE__ */ React.createElement("div", { className: "pending-file-chip", key: (file.name || "file") + "-" + String(file.lastModified || index) }, /* @__PURE__ */ React.createElement("span", { className: "pending-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"), /* @__PURE__ */ React.createElement("span", { className: "pending-file-name" }, file.name), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "pending-file-remove",
|
|
"aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0444\u0430\u0439\u043B " + file.name,
|
|
onClick: () => onRemoveSelectedFile(index)
|
|
},
|
|
"\xD7"
|
|
))), /* @__PURE__ */ React.createElement("button", { type: "button", className: "btn secondary btn-sm", onClick: onClearSelectedFiles }, "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F")) : null, /* @__PURE__ */ React.createElement("div", { className: "request-chat-composer-actions" }, canRequestData ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "btn secondary btn-sm",
|
|
type: "button",
|
|
onClick: openCreateDataRequestModal,
|
|
disabled: loading || fileUploading
|
|
},
|
|
"\u0417\u0430\u043F\u0440\u043E\u0441\u0438\u0442\u044C"
|
|
) : null, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "icon-btn file-action-btn composer-attach-btn",
|
|
type: "button",
|
|
"data-tooltip": "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B",
|
|
"aria-label": "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B",
|
|
onClick: () => {
|
|
var _a2;
|
|
return (_a2 = fileInputRef.current) == null ? void 0 : _a2.click();
|
|
},
|
|
disabled: loading || fileUploading
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M8.6 13.8 15 7.4a3 3 0 0 1 4.2 4.2l-8.1 8.1a5 5 0 1 1-7.1-7.1l8.6-8.6a1 1 0 0 1 1.4 1.4l-8.6 8.6a3 3 0 1 0 4.2 4.2l8.1-8.1a1 1 0 0 0-1.4-1.4l-6.4 6.4a1 1 0 0 1-1.4-1.4z",
|
|
fill: "currentColor"
|
|
}
|
|
))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "btn",
|
|
id: idMap.sendButton,
|
|
type: "submit",
|
|
disabled: loading || fileUploading || !canSubmit
|
|
},
|
|
"\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C"
|
|
)))) : /* @__PURE__ */ React.createElement("div", { className: "request-files-tab" }, /* @__PURE__ */ React.createElement("ul", { className: "simple-list request-modal-list", id: idMap.filesList }, safeAttachments.length ? safeAttachments.map((item) => /* @__PURE__ */ React.createElement("li", { key: String(item.id) }, /* @__PURE__ */ React.createElement("div", null, item.file_name || "\u0424\u0430\u0439\u043B"), /* @__PURE__ */ React.createElement("div", { className: "muted request-modal-item-meta" }, String(item.mime_type || "application/octet-stream") + " \u2022 " + fmtBytes(item.size_bytes) + " \u2022 " + fmtDate(item.created_at)), /* @__PURE__ */ React.createElement("div", { className: "request-file-actions" }, item.download_url && detectAttachmentPreviewKind(item.file_name, item.mime_type) !== "none" ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "icon-btn file-action-btn",
|
|
type: "button",
|
|
"data-tooltip": "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
|
|
onClick: () => openPreview(item),
|
|
"aria-label": "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440"
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M12 5C6.8 5 3 9.2 2 12c1 2.8 4.8 7 10 7s9-4.2 10-7c-1-2.8-4.8-7-10-7zm0 11a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm0-2.2A1.8 1.8 0 1 0 12 10a1.8 1.8 0 0 0 0 3.8z",
|
|
fill: "currentColor"
|
|
}
|
|
))
|
|
) : null, item.download_url ? /* @__PURE__ */ React.createElement(
|
|
"a",
|
|
{
|
|
className: "icon-btn file-action-btn request-file-link-icon",
|
|
"data-tooltip": "\u0421\u043A\u0430\u0447\u0430\u0442\u044C",
|
|
"aria-label": "\u0421\u043A\u0430\u0447\u0430\u0442\u044C: " + String(item.file_name || "\u0444\u0430\u0439\u043B"),
|
|
href: item.download_url,
|
|
target: "_blank",
|
|
rel: "noreferrer"
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M12 3a1 1 0 0 1 1 1v8.17l2.58-2.58a1 1 0 1 1 1.42 1.42l-4.3 4.3a1 1 0 0 1-1.4 0l-4.3-4.3a1 1 0 0 1 1.42-1.42L11 12.17V4a1 1 0 0 1 1-1zm-7 14a1 1 0 0 1 1 1v1h12v-1a1 1 0 1 1 2 0v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z",
|
|
fill: "currentColor"
|
|
}
|
|
))
|
|
) : null))) : /* @__PURE__ */ React.createElement("li", { className: "muted" }, "\u0424\u0430\u0439\u043B\u043E\u0432 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442")), /* @__PURE__ */ React.createElement("div", { className: "request-files-tab-actions" }, /* @__PURE__ */ React.createElement("span", { className: "muted" }, "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439: " + String(safeMessages.length) + " \u2022 \u041E\u0431\u0449\u0438\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 \u0444\u0430\u0439\u043B\u043E\u0432: " + fmtBytes(totalFilesBytes)))))), StatusLine ? /* @__PURE__ */ React.createElement(StatusLine, { status }) : null, AttachmentPreviewModal ? /* @__PURE__ */ React.createElement(
|
|
AttachmentPreviewModal,
|
|
{
|
|
open: preview.open,
|
|
title: "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0444\u0430\u0439\u043B\u0430",
|
|
url: preview.url,
|
|
fileName: preview.fileName,
|
|
mimeType: preview.mimeType,
|
|
onClose: closePreview
|
|
}
|
|
) : null, /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "overlay" + (clientDataModal.open ? " open" : ""),
|
|
onClick: closeClientDataModal,
|
|
"aria-hidden": clientDataModal.open ? "false" : "true",
|
|
id: idMap.dataRequestOverlay
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "modal request-data-summary-modal data-request-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, "\u0417\u0430\u043F\u0440\u043E\u0441 \u0434\u0430\u043D\u043D\u044B\u0445"), /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-subtitle" }, (row == null ? void 0 : row.track_number) ? "\u0417\u0430\u044F\u0432\u043A\u0430 " + String(row.track_number) : "\u0417\u0430\u043F\u043E\u043B\u043D\u0438\u0442\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443 \u044E\u0440\u0438\u0441\u0442\u0430")), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: closeClientDataModal, "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement("form", { className: "stack", onSubmit: submitClientDataModal }, /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-list", id: idMap.dataRequestItems }, clientDataModal.loading ? /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...") : (clientDataModal.items || []).length ? (clientDataModal.items || []).map((item, index) => {
|
|
const fieldType = String((item == null ? void 0 : item.field_type) || "string").toLowerCase();
|
|
const fileMeta = item == null ? void 0 : item.value_file;
|
|
return /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-row", key: String(item.localId || index) }, /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-label" }, String(index + 1) + ". " + String((item == null ? void 0 : item.label) || (item == null ? void 0 : item.key) || "\u041F\u043E\u043B\u0435")), /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-value" }, fieldType === "text" ? /* @__PURE__ */ React.createElement(
|
|
"textarea",
|
|
{
|
|
value: String((item == null ? void 0 : item.value_text) || ""),
|
|
onChange: (event) => updateClientDataItem(item.localId, { value_text: event.target.value }),
|
|
rows: 3,
|
|
disabled: clientDataModal.saving || clientDataModal.loading
|
|
}
|
|
) : fieldType === "date" ? /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
type: "date",
|
|
value: String((item == null ? void 0 : item.value_text) || "").slice(0, 10),
|
|
onChange: (event) => updateClientDataItem(item.localId, { value_text: event.target.value }),
|
|
disabled: clientDataModal.saving || clientDataModal.loading
|
|
}
|
|
) : fieldType === "number" ? /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
type: "number",
|
|
step: "any",
|
|
value: String((item == null ? void 0 : item.value_text) || ""),
|
|
onChange: (event) => updateClientDataItem(item.localId, { value_text: event.target.value }),
|
|
disabled: clientDataModal.saving || clientDataModal.loading
|
|
}
|
|
) : fieldType === "file" ? /* @__PURE__ */ React.createElement("div", { className: "stack" }, fileMeta && fileMeta.download_url ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "chat-message-file-chip",
|
|
onClick: () => openAttachmentFromMessage(fileMeta)
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"),
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-name" }, String(fileMeta.file_name || "\u0424\u0430\u0439\u043B"))
|
|
) : null, /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
type: "file",
|
|
onChange: (event) => updateClientDataItem(item.localId, {
|
|
pendingFile: event.target.files && event.target.files[0] ? event.target.files[0] : null
|
|
}),
|
|
disabled: clientDataModal.saving || clientDataModal.loading
|
|
}
|
|
), (item == null ? void 0 : item.pendingFile) ? /* @__PURE__ */ React.createElement("span", { className: "muted" }, String(item.pendingFile.name || "")) : null) : /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
type: "text",
|
|
value: String((item == null ? void 0 : item.value_text) || ""),
|
|
onChange: (event) => updateClientDataItem(item.localId, { value_text: event.target.value }),
|
|
disabled: clientDataModal.saving || clientDataModal.loading
|
|
}
|
|
)));
|
|
}) : /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u041D\u0435\u0442 \u043F\u043E\u043B\u0435\u0439 \u0434\u043B\u044F \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F.")), clientDataModal.error ? /* @__PURE__ */ React.createElement("div", { className: "status error" }, clientDataModal.error) : null, /* @__PURE__ */ React.createElement("div", { className: "request-data-status" + (clientDataModal.status ? " ok" : ""), id: idMap.dataRequestStatus }, clientDataModal.status || ""), /* @__PURE__ */ React.createElement("div", { className: "modal-actions modal-actions-right" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "submit",
|
|
className: "btn btn-sm request-data-submit-btn",
|
|
id: idMap.dataRequestSave,
|
|
disabled: clientDataModal.loading || clientDataModal.saving
|
|
},
|
|
clientDataModal.saving ? "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435..." : "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C"
|
|
))))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "overlay" + (statusChangeModal.open ? " open" : ""),
|
|
onClick: closeStatusChangeModal,
|
|
"aria-hidden": statusChangeModal.open ? "false" : "true"
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "modal request-status-change-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, "\u0421\u043C\u0435\u043D\u0430 \u0441\u0442\u0430\u0442\u0443\u0441\u0430"), /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-subtitle" }, (row == null ? void 0 : row.track_number) ? "\u0417\u0430\u044F\u0432\u043A\u0430 " + String(row.track_number) : "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438 \u0432\u0430\u0436\u043D\u0443\u044E \u0434\u0430\u0442\u0443")), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: closeStatusChangeModal, "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
ref: statusChangeFileInputRef,
|
|
type: "file",
|
|
multiple: true,
|
|
onChange: (event) => {
|
|
appendStatusChangeFiles(Array.from(event.target && event.target.files || []));
|
|
event.target.value = "";
|
|
},
|
|
style: { position: "absolute", width: "1px", height: "1px", opacity: 0, pointerEvents: "none" }
|
|
}
|
|
), /* @__PURE__ */ React.createElement("form", { className: "stack", onSubmit: submitStatusChange }, /* @__PURE__ */ React.createElement("div", { className: "request-status-change-grid" }, /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "status-change-next-status" }, "\u041D\u043E\u0432\u044B\u0439 \u0441\u0442\u0430\u0442\u0443\u0441"), /* @__PURE__ */ React.createElement(
|
|
"select",
|
|
{
|
|
id: "status-change-next-status",
|
|
value: statusChangeModal.statusCode,
|
|
onChange: (event) => setStatusChangeModal((prev) => ({ ...prev, statusCode: event.target.value, error: "" })),
|
|
disabled: statusChangeModal.saving || loading
|
|
},
|
|
/* @__PURE__ */ React.createElement("option", { value: "" }, "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0442\u0430\u0442\u0443\u0441"),
|
|
statusOptions.filter((item) => item.code !== String((row == null ? void 0 : row.status_code) || "").trim()).filter(
|
|
(item) => Array.isArray(statusChangeModal.allowedStatusCodes) && statusChangeModal.allowedStatusCodes.length ? statusChangeModal.allowedStatusCodes.includes(item.code) : true
|
|
).map((item) => /* @__PURE__ */ React.createElement("option", { key: item.code, value: item.code }, item.name + (item.groupName ? " \u2022 " + item.groupName : "")))
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "status-change-important-date" }, "\u0412\u0430\u0436\u043D\u0430\u044F \u0434\u0430\u0442\u0430 (\u0434\u0435\u0434\u043B\u0430\u0439\u043D)"), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: "status-change-important-date",
|
|
type: "datetime-local",
|
|
value: statusChangeModal.importantDateAt,
|
|
onChange: (event) => setStatusChangeModal((prev) => ({ ...prev, importantDateAt: event.target.value, error: "" })),
|
|
disabled: statusChangeModal.saving || loading
|
|
}
|
|
))), /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "status-change-comment" }, "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043A \u0441\u043C\u0435\u043D\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430"), /* @__PURE__ */ React.createElement(
|
|
"textarea",
|
|
{
|
|
id: "status-change-comment",
|
|
placeholder: "\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u0431\u0443\u0434\u0435\u0442 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0438 \u0447\u0430\u0442 (\u0435\u0441\u043B\u0438 \u0443\u043A\u0430\u0437\u0430\u043D)",
|
|
value: statusChangeModal.comment,
|
|
onChange: (event) => setStatusChangeModal((prev) => ({ ...prev, comment: event.target.value })),
|
|
disabled: statusChangeModal.saving || loading
|
|
}
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "request-status-change-files" }, /* @__PURE__ */ React.createElement("div", { className: "request-status-change-files-head" }, /* @__PURE__ */ React.createElement("b", null, "\u0412\u043B\u043E\u0436\u0435\u043D\u0438\u044F"), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn file-action-btn",
|
|
"data-tooltip": "\u041F\u0440\u0438\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0444\u0430\u0439\u043B\u044B",
|
|
onClick: () => {
|
|
var _a2;
|
|
return (_a2 = statusChangeFileInputRef.current) == null ? void 0 : _a2.click();
|
|
},
|
|
disabled: statusChangeModal.saving || loading
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M8.6 13.8 15 7.4a3 3 0 0 1 4.2 4.2l-8.1 8.1a5 5 0 1 1-7.1-7.1l8.6-8.6a1 1 0 0 1 1.4 1.4l-8.6 8.6a3 3 0 1 0 4.2 4.2l8.1-8.1a1 1 0 0 0-1.4-1.4l-6.4 6.4a1 1 0 0 1-1.4-1.4z",
|
|
fill: "currentColor"
|
|
}
|
|
))
|
|
)), Array.isArray(statusChangeModal.files) && statusChangeModal.files.length ? /* @__PURE__ */ React.createElement("div", { className: "request-pending-files" }, statusChangeModal.files.map((file, index) => /* @__PURE__ */ React.createElement("div", { className: "pending-file-chip", key: (file.name || "file") + "-" + String(file.lastModified || index) }, /* @__PURE__ */ React.createElement("span", { className: "pending-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"), /* @__PURE__ */ React.createElement("span", { className: "pending-file-name" }, file.name), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "pending-file-remove",
|
|
"aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0444\u0430\u0439\u043B " + file.name,
|
|
onClick: () => removeStatusChangeFile(index)
|
|
},
|
|
"\xD7"
|
|
)))) : /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u0424\u0430\u0439\u043B\u044B \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B")), /* @__PURE__ */ React.createElement("div", { className: "request-status-history-block" }, /* @__PURE__ */ React.createElement("div", { className: "request-status-history-head" }, /* @__PURE__ */ React.createElement("b", null, "\u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u0432"), /* @__PURE__ */ React.createElement("span", { className: "muted" }, safeStatusHistory.length ? String(safeStatusHistory.length) + " \u0437\u0430\u043F\u0438\u0441\u0435\u0439" : "\u041D\u0435\u0442 \u0437\u0430\u043F\u0438\u0441\u0435\u0439")), /* @__PURE__ */ React.createElement("ol", { className: "request-route-list request-status-history-list" }, safeStatusHistory.length ? safeStatusHistory.map((item, index) => {
|
|
const statusCode = String((item == null ? void 0 : item.to_status) || "");
|
|
const statusMeta = statusByCode.get(statusCode);
|
|
const itemClass = "route-item request-status-history-route-item " + (index === 0 ? "current" : "completed");
|
|
return /* @__PURE__ */ React.createElement("li", { key: String((item == null ? void 0 : item.id) || index), className: itemClass }, /* @__PURE__ */ React.createElement("span", { className: "route-dot" }), /* @__PURE__ */ React.createElement("div", { className: "route-body" }, /* @__PURE__ */ React.createElement("div", { className: "request-status-history-row" }, /* @__PURE__ */ React.createElement("b", null, resolveStatusDisplayName(statusCode, (item == null ? void 0 : item.to_status_name) || (statusMeta == null ? void 0 : statusMeta.name) || "")), (statusMeta == null ? void 0 : statusMeta.isTerminal) ? /* @__PURE__ */ React.createElement("span", { className: "request-status-history-chip" }, "\u0422\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0439") : null), /* @__PURE__ */ React.createElement("div", { className: "muted route-time" }, fmtShortDateTime(item == null ? void 0 : item.changed_at)), /* @__PURE__ */ React.createElement("div", { className: "request-status-history-meta" }, /* @__PURE__ */ React.createElement("span", null, "\u0412\u0430\u0436\u043D\u0430\u044F \u0434\u0430\u0442\u0430: " + fmtShortDateTime(item == null ? void 0 : item.important_date_at)), /* @__PURE__ */ React.createElement("span", null, "\u0414\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C: " + formatDuration(item == null ? void 0 : item.duration_seconds))), String((item == null ? void 0 : item.comment) || "").trim() ? /* @__PURE__ */ React.createElement("div", { className: "request-status-history-comment" }, String(item.comment)) : null));
|
|
}) : /* @__PURE__ */ React.createElement("li", { className: "muted" }, "\u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u0432 \u043F\u043E\u043A\u0430 \u043F\u0443\u0441\u0442\u0430\u044F"))), statusChangeModal.error ? /* @__PURE__ */ React.createElement("div", { className: "status error" }, statusChangeModal.error) : null, /* @__PURE__ */ React.createElement("div", { className: "modal-actions modal-actions-right" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "submit",
|
|
className: "btn btn-sm request-data-submit-btn",
|
|
disabled: statusChangeModal.saving || loading
|
|
},
|
|
statusChangeModal.saving ? "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435..." : "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C"
|
|
))))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "overlay" + (financeOpen ? " open" : ""),
|
|
onClick: closeFinanceModal,
|
|
"aria-hidden": financeOpen ? "false" : "true"
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "modal request-finance-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, "\u0424\u0438\u043D\u0430\u043D\u0441\u044B \u0437\u0430\u044F\u0432\u043A\u0438"), /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-subtitle" }, (row == null ? void 0 : row.track_number) ? "\u0417\u0430\u044F\u0432\u043A\u0430 " + String(row.track_number) : "\u0414\u0430\u043D\u043D\u044B\u0435 \u043F\u043E \u0437\u0430\u044F\u0432\u043A\u0435")), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: closeFinanceModal, "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement("div", { className: "request-card-grid request-finance-grid" }, /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0421\u0442\u043E\u0438\u043C\u043E\u0441\u0442\u044C"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtAmount((_a = finance == null ? void 0 : finance.request_cost) != null ? _a : row == null ? void 0 : row.request_cost))), /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u041E\u043F\u043B\u0430\u0447\u0435\u043D\u043E"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtAmount(finance == null ? void 0 : finance.paid_total))), /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0414\u0430\u0442\u0430 \u043E\u043F\u043B\u0430\u0442\u044B"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtShortDateTime((_b = finance == null ? void 0 : finance.last_paid_at) != null ? _b : row == null ? void 0 : row.paid_at))), canSeeRate ? /* @__PURE__ */ React.createElement("div", { className: "request-field" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0421\u0442\u0430\u0432\u043A\u0430"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtAmount((_c = finance == null ? void 0 : finance.effective_rate) != null ? _c : row == null ? void 0 : row.effective_rate))) : null), typeof onIssueInvoice === "function" ? /* @__PURE__ */ React.createElement("div", { className: "request-finance-actions" }, !financeIssueForm.open ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "btn btn-sm",
|
|
onClick: openFinanceIssueForm,
|
|
disabled: loading || !row
|
|
},
|
|
"\u0412\u044B\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0447\u0435\u0442"
|
|
) : /* @__PURE__ */ React.createElement("form", { className: "stack request-finance-issue-form", onSubmit: submitFinanceIssueForm }, /* @__PURE__ */ React.createElement("div", { className: "request-finance-issue-grid" }, /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "request-finance-invoice-amount" }, "\u0421\u0443\u043C\u043C\u0430"), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: "request-finance-invoice-amount",
|
|
type: "number",
|
|
min: "0.01",
|
|
step: "0.01",
|
|
value: financeIssueForm.amount,
|
|
onChange: (event) => setFinanceIssueForm((prev) => ({ ...prev, amount: event.target.value, error: "" })),
|
|
disabled: financeIssueForm.saving || loading,
|
|
placeholder: "0.00"
|
|
}
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "request-finance-invoice-payer" }, "\u041F\u043B\u0430\u0442\u0435\u043B\u044C\u0449\u0438\u043A"), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: "request-finance-invoice-payer",
|
|
type: "text",
|
|
value: financeIssueForm.payerDisplayName,
|
|
onChange: (event) => setFinanceIssueForm((prev) => ({ ...prev, payerDisplayName: event.target.value, error: "" })),
|
|
disabled: financeIssueForm.saving || loading,
|
|
placeholder: "\u0424\u0418\u041E / \u043A\u043E\u043C\u043F\u0430\u043D\u0438\u044F"
|
|
}
|
|
))), /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "request-finance-invoice-service" }, "\u0423\u0441\u043B\u0443\u0433\u0430"), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: "request-finance-invoice-service",
|
|
type: "text",
|
|
value: financeIssueForm.serviceDescription,
|
|
onChange: (event) => setFinanceIssueForm((prev) => ({ ...prev, serviceDescription: event.target.value, error: "" })),
|
|
disabled: financeIssueForm.saving || loading,
|
|
placeholder: "\u042E\u0440\u0438\u0434\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0443\u0441\u043B\u0443\u0433\u0438"
|
|
}
|
|
)), financeIssueForm.error ? /* @__PURE__ */ React.createElement("div", { className: "status error" }, financeIssueForm.error) : null, /* @__PURE__ */ React.createElement("div", { className: "modal-actions modal-actions-right request-finance-actions-inline" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "btn secondary btn-sm", onClick: closeFinanceIssueForm, disabled: financeIssueForm.saving }, "\u041E\u0442\u043C\u0435\u043D\u0430"), /* @__PURE__ */ React.createElement("button", { type: "submit", className: "btn btn-sm", disabled: financeIssueForm.saving || loading }, financeIssueForm.saving ? "\u0412\u044B\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u0438\u0435..." : "\u0412\u044B\u0441\u0442\u0430\u0432\u0438\u0442\u044C")))) : null, /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoices" }, /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoices-head" }, /* @__PURE__ */ React.createElement("h4", null, "\u0421\u0447\u0435\u0442\u0430"), /* @__PURE__ */ React.createElement("span", { className: "muted" }, safeInvoices.length ? String(safeInvoices.length) + " \u0448\u0442." : "\u041D\u0435\u0442 \u0432\u044B\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0441\u0447\u0435\u0442\u043E\u0432")), safeInvoices.length ? /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoice-list" }, safeInvoices.map((item) => /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoice-row", key: String((item == null ? void 0 : item.id) || (item == null ? void 0 : item.invoice_number) || (item == null ? void 0 : item.issued_at) || "-") }, /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoice-meta" }, /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoice-number" }, /* @__PURE__ */ React.createElement("code", null, String((item == null ? void 0 : item.invoice_number) || "-"))), /* @__PURE__ */ React.createElement("div", { className: "request-finance-invoice-details" }, /* @__PURE__ */ React.createElement("span", null, invoiceStatusLabel(item == null ? void 0 : item.status)), /* @__PURE__ */ React.createElement("span", null, fmtAmount(item == null ? void 0 : item.amount) + " " + String((item == null ? void 0 : item.currency) || "RUB")), /* @__PURE__ */ React.createElement("span", null, "\u0421\u043E\u0437\u0434\u0430\u043D: " + fmtDate(item == null ? void 0 : item.issued_at)), /* @__PURE__ */ React.createElement("span", null, "\u041E\u043F\u043B\u0430\u0447\u0435\u043D: " + fmtDate(item == null ? void 0 : item.paid_at)))), typeof onDownloadInvoicePdf === "function" ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn request-finance-invoice-download-btn",
|
|
onClick: () => onDownloadInvoicePdf(item),
|
|
disabled: loading,
|
|
"aria-label": "\u0421\u043A\u0430\u0447\u0430\u0442\u044C \u0441\u0447\u0435\u0442 PDF",
|
|
"data-tooltip": "\u0421\u043A\u0430\u0447\u0430\u0442\u044C PDF"
|
|
},
|
|
"\u2B07"
|
|
) : null))) : /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-empty" }, "\u0421\u0447\u0435\u0442\u0430 \u043F\u043E \u0437\u0430\u044F\u0432\u043A\u0435 \u043F\u043E\u043A\u0430 \u043D\u0435 \u0432\u044B\u0441\u0442\u0430\u0432\u043B\u044F\u043B\u0438\u0441\u044C")))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "overlay" + (descriptionOpen ? " open" : ""),
|
|
onClick: () => setDescriptionOpen(false),
|
|
"aria-hidden": descriptionOpen ? "false" : "true"
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "modal request-description-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, (row == null ? void 0 : row.track_number) ? "\u0417\u0430\u044F\u0432\u043A\u0430 " + String(row.track_number) : "\u0417\u0430\u044F\u0432\u043A\u0430"), /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-headline" }, /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-subtitle" }, String((row == null ? void 0 : row.topic_name) || (row == null ? void 0 : row.topic_code) || "\u0422\u0435\u043C\u0430 \u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u0430")), /* @__PURE__ */ React.createElement("span", { className: "request-description-status-chip" }, currentStatusName))), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: () => setDescriptionOpen(false), "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-body" }, /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-main" }, /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-title" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B")), /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-text" }, (row == null ? void 0 : row.description) ? String(row.description) : "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E")), /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-meta-wrap" }, /* @__PURE__ */ React.createElement("div", { className: "request-description-modal-meta" }, /* @__PURE__ */ React.createElement("div", { className: "request-description-meta-item" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u041A\u043B\u0438\u0435\u043D\u0442"), /* @__PURE__ */ React.createElement(
|
|
"span",
|
|
{
|
|
className: "request-field-value" + (clientHasPhone ? " has-tooltip request-contact-value" : ""),
|
|
"data-tooltip": clientHasPhone ? clientPhone : void 0
|
|
},
|
|
clientLabel
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "request-description-meta-item align-right" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u042E\u0440\u0438\u0441\u0442"), /* @__PURE__ */ React.createElement(
|
|
"span",
|
|
{
|
|
className: "request-field-value" + (lawyerHasPhone ? " has-tooltip request-contact-value" : ""),
|
|
"data-tooltip": lawyerHasPhone ? lawyerPhone : void 0
|
|
},
|
|
lawyerLabel
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "request-description-meta-item" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0421\u043E\u0437\u0434\u0430\u043D\u0430"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtShortDateTime(row == null ? void 0 : row.created_at))), /* @__PURE__ */ React.createElement("div", { className: "request-description-meta-item align-right" }, /* @__PURE__ */ React.createElement("span", { className: "request-field-label" }, "\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0430"), /* @__PURE__ */ React.createElement("span", { className: "request-field-value" }, fmtShortDateTime(row == null ? void 0 : row.updated_at)))))))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "overlay" + (dataRequestModal.open ? " open" : ""),
|
|
onClick: closeDataRequestModal,
|
|
"aria-hidden": dataRequestModal.open ? "false" : "true"
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "modal request-data-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, dataRequestModal.messageId ? "\u0420\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0434\u0430\u043D\u043D\u044B\u0445" : "\u0417\u0430\u043F\u0440\u043E\u0441 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445 \u0434\u0430\u043D\u043D\u044B\u0445"), /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-subtitle" }, (row == null ? void 0 : row.track_number) ? "\u0417\u0430\u044F\u0432\u043A\u0430 " + String(row.track_number) : "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043F\u043E\u043B\u044F \u0434\u043B\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430")), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: closeDataRequestModal, "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement("div", { className: "stack" }, /* @__PURE__ */ React.createElement("div", { className: "request-data-modal-grid" }, /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "request-data-request-template-select" }, "\u0428\u0430\u0431\u043B\u043E\u043D \u0437\u0430\u043F\u0440\u043E\u0441\u0430 (\u043F\u043E\u0438\u0441\u043A)"), /* @__PURE__ */ React.createElement("div", { className: "request-data-combobox" }, /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: "request-data-request-template-select",
|
|
name: "request_template_search_nohistory",
|
|
type: "text",
|
|
value: dataRequestModal.requestTemplateQuery,
|
|
onChange: (event) => setDataRequestModal((prev) => ({
|
|
...prev,
|
|
requestTemplateQuery: event.target.value,
|
|
selectedRequestTemplateId: "",
|
|
templateStatus: "",
|
|
error: ""
|
|
})),
|
|
onFocus: (event) => {
|
|
event.currentTarget.removeAttribute("readonly");
|
|
setRequestTemplateSuggestOpen(true);
|
|
},
|
|
onBlur: (event) => {
|
|
event.currentTarget.setAttribute("readonly", "readonly");
|
|
window.setTimeout(() => setRequestTemplateSuggestOpen(false), 120);
|
|
},
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate,
|
|
placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0448\u0430\u0431\u043B\u043E\u043D\u0430",
|
|
readOnly: true,
|
|
autoComplete: "new-password",
|
|
autoCorrect: "off",
|
|
autoCapitalize: "none",
|
|
spellCheck: false,
|
|
"aria-autocomplete": "list",
|
|
"data-1p-ignore": "true",
|
|
"data-lpignore": "true"
|
|
}
|
|
), requestTemplateBadge ? /* @__PURE__ */ React.createElement("span", { className: "request-data-template-badge " + requestTemplateBadge.kind }, requestTemplateBadge.label) : null, requestTemplateSuggestOpen && filteredRequestTemplates.length ? /* @__PURE__ */ React.createElement("div", { className: "request-data-suggest-list", role: "listbox", "aria-label": "\u0428\u0430\u0431\u043B\u043E\u043D\u044B \u0437\u0430\u043F\u0440\u043E\u0441\u0430" }, filteredRequestTemplates.map((tpl) => /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
key: String(tpl.id),
|
|
type: "button",
|
|
className: "request-data-suggest-item",
|
|
onMouseDown: (event) => {
|
|
event.preventDefault();
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
requestTemplateQuery: String(tpl.name || ""),
|
|
selectedRequestTemplateId: String(tpl.id || ""),
|
|
error: "",
|
|
templateStatus: ""
|
|
}));
|
|
setRequestTemplateSuggestOpen(false);
|
|
void applyRequestTemplateById(String(tpl.id || ""), String(tpl.name || ""));
|
|
}
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", null, String(tpl.name || "\u0428\u0430\u0431\u043B\u043E\u043D"))
|
|
))) : null)), /* @__PURE__ */ React.createElement("div", { className: "request-data-modal-actions-inline" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn",
|
|
"data-tooltip": !canSaveSelectedRequestTemplate ? "\u0427\u0443\u0436\u043E\u0439 \u0448\u0430\u0431\u043B\u043E\u043D \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0434\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F" : requestTemplateActionMode === "save" ? "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0448\u0430\u0431\u043B\u043E\u043D" : requestTemplateActionMode === "create" ? "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u0448\u0430\u0431\u043B\u043E\u043D" : "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0448\u0430\u0431\u043B\u043E\u043D\u0430",
|
|
onClick: saveCurrentDataRequestTemplate,
|
|
disabled: !canSaveSelectedRequestTemplate || dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate
|
|
},
|
|
dataRequestModal.savingTemplate ? "\u2026" : requestTemplateActionMode === "create" ? "\u271A" : "\u{1F4BE}"
|
|
))), dataRequestModal.templateStatus ? /* @__PURE__ */ React.createElement("div", { className: "status ok" }, dataRequestModal.templateStatus) : null, canRequestData && dataRequestModal.messageId ? /* @__PURE__ */ React.createElement("div", { className: "request-data-progress-line" }, /* @__PURE__ */ React.createElement("span", { className: "request-data-progress-chip" }, "\u0417\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u043C: " + String(dataRequestProgress.filled) + " / " + String(dataRequestProgress.total))) : null, /* @__PURE__ */ React.createElement("div", { className: "request-data-modal-grid" }, /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "request-data-template-select" }, "\u041F\u043E\u043B\u0435 \u0434\u0430\u043D\u043D\u044B\u0445 (\u043F\u043E\u0438\u0441\u043A \u043F\u043E \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0443)"), /* @__PURE__ */ React.createElement("div", { className: "request-data-combobox" }, /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
id: "request-data-template-select",
|
|
name: "request_field_search_nohistory",
|
|
type: "text",
|
|
value: dataRequestModal.catalogFieldQuery,
|
|
onChange: (event) => setDataRequestModal((prev) => ({
|
|
...prev,
|
|
catalogFieldQuery: event.target.value,
|
|
selectedCatalogTemplateId: "",
|
|
templateStatus: "",
|
|
error: ""
|
|
})),
|
|
onFocus: (event) => {
|
|
event.currentTarget.removeAttribute("readonly");
|
|
setCatalogFieldSuggestOpen(true);
|
|
},
|
|
onBlur: (event) => {
|
|
event.currentTarget.setAttribute("readonly", "readonly");
|
|
window.setTimeout(() => setCatalogFieldSuggestOpen(false), 120);
|
|
},
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate,
|
|
placeholder: "\u041D\u0430\u0447\u043D\u0438\u0442\u0435 \u0432\u0432\u043E\u0434\u0438\u0442\u044C \u043D\u0430\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u0438\u0435 \u043F\u043E\u043B\u044F",
|
|
readOnly: true,
|
|
autoComplete: "new-password",
|
|
autoCorrect: "off",
|
|
autoCapitalize: "none",
|
|
spellCheck: false,
|
|
"aria-autocomplete": "list",
|
|
"data-1p-ignore": "true",
|
|
"data-lpignore": "true"
|
|
}
|
|
), catalogFieldSuggestOpen && filteredCatalogFields.length ? /* @__PURE__ */ React.createElement("div", { className: "request-data-suggest-list", role: "listbox", "aria-label": "\u041F\u043E\u043B\u044F \u0434\u0430\u043D\u043D\u044B\u0445" }, filteredCatalogFields.map((tpl) => /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
key: String(tpl.id),
|
|
type: "button",
|
|
className: "request-data-suggest-item",
|
|
onMouseDown: (event) => {
|
|
event.preventDefault();
|
|
setDataRequestModal((prev) => ({
|
|
...prev,
|
|
catalogFieldQuery: String(tpl.label || tpl.key || ""),
|
|
selectedCatalogTemplateId: String(tpl.id || ""),
|
|
error: "",
|
|
templateStatus: ""
|
|
}));
|
|
setCatalogFieldSuggestOpen(false);
|
|
}
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", null, String(tpl.label || tpl.key)),
|
|
/* @__PURE__ */ React.createElement("small", null, String(tpl.value_type || "string"))
|
|
))) : null)), /* @__PURE__ */ React.createElement("div", { className: "request-data-modal-actions-inline" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn",
|
|
"data-tooltip": catalogFieldActionMode === "add" ? "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u043E\u043B\u0435 \u0438\u0437 \u0441\u043F\u0440\u0430\u0432\u043E\u0447\u043D\u0438\u043A\u0430" : "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u043E\u0435 \u043F\u043E\u043B\u0435",
|
|
onClick: addSelectedTemplateRow,
|
|
disabled: !String(dataRequestModal.catalogFieldQuery || "").trim() && !selectedCatalogFieldCandidate || dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate
|
|
},
|
|
catalogFieldActionMode === "add" ? "+" : "\u271A"
|
|
))), /* @__PURE__ */ React.createElement("div", { className: "request-data-rows" }, (dataRequestModal.rows || []).length ? (dataRequestModal.rows || []).map((rowItem, idx) => /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "request-data-row" + (String(draggedRequestRowId) === String(rowItem.localId) ? " dragging" : "") + (String(dragOverRequestRowId) === String(rowItem.localId) && String(draggedRequestRowId) !== String(rowItem.localId) ? " drag-over" : "") + (viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled) ? " row-locked" : ""),
|
|
key: rowItem.localId,
|
|
onDragOver: (event) => {
|
|
if (!draggedRequestRowId) return;
|
|
event.preventDefault();
|
|
if (viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)) return;
|
|
setDragOverRequestRowId(String(rowItem.localId || ""));
|
|
},
|
|
onDrop: (event) => {
|
|
if (!draggedRequestRowId) return;
|
|
event.preventDefault();
|
|
if (viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)) return;
|
|
moveDataRequestRowToIndex(draggedRequestRowId, idx);
|
|
handleRequestRowDragEnd();
|
|
}
|
|
},
|
|
/* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn request-data-row-index-handle",
|
|
"data-tooltip": viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled) ? "\u0417\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u043B\u0435: \u043F\u0435\u0440\u0435\u043C\u0435\u0449\u0435\u043D\u0438\u0435 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E" : "\u041F\u0435\u0440\u0435\u0442\u0430\u0449\u0438\u0442\u0435 \u0434\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u043E\u0440\u044F\u0434\u043A\u0430",
|
|
draggable: !(viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)),
|
|
onDragStart: (event) => handleRequestRowDragStart(event, rowItem, viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)),
|
|
onDragEnd: handleRequestRowDragEnd,
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate || viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled),
|
|
"aria-label": "\u041F\u043E\u0440\u044F\u0434\u043E\u043A \u043F\u043E\u043B\u044F " + String(idx + 1)
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", null, idx + 1)
|
|
),
|
|
/* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", null, "\u041D\u0430\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u0438\u0435"), /* @__PURE__ */ React.createElement(
|
|
"input",
|
|
{
|
|
value: rowItem.label,
|
|
onChange: (event) => updateDataRequestRow(rowItem.localId, { label: event.target.value }),
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate || viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)
|
|
}
|
|
)),
|
|
/* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", null, "\u0422\u0438\u043F"), /* @__PURE__ */ React.createElement(
|
|
"select",
|
|
{
|
|
value: rowItem.field_type || "string",
|
|
onChange: (event) => updateDataRequestRow(rowItem.localId, { field_type: event.target.value }),
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate || viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)
|
|
},
|
|
requestDataTypeOptions.map((option) => /* @__PURE__ */ React.createElement("option", { key: option.value, value: option.value }, option.label))
|
|
)),
|
|
/* @__PURE__ */ React.createElement("div", { className: "request-data-row-controls" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "icon-btn danger request-data-row-action-btn",
|
|
"data-tooltip": viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled) ? "\u042E\u0440\u0438\u0441\u0442 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043D\u043E\u0435 \u043F\u043E\u043B\u0435" : "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u043E\u043B\u0435",
|
|
onClick: () => removeDataRequestRow(rowItem.localId),
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate || viewerRoleCode === "LAWYER" && (rowItem == null ? void 0 : rowItem.is_filled)
|
|
},
|
|
"\xD7"
|
|
)),
|
|
canRequestData && ((rowItem == null ? void 0 : rowItem.is_filled) || String((rowItem == null ? void 0 : rowItem.value_text) || "").trim()) ? /* @__PURE__ */ React.createElement("div", { className: "request-data-row-client-value" }, /* @__PURE__ */ React.createElement("span", { className: "request-data-row-client-label" }, "\u0417\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u043C:"), String((rowItem == null ? void 0 : rowItem.field_type) || "").toLowerCase() === "file" ? (rowItem == null ? void 0 : rowItem.value_file) && rowItem.value_file.download_url ? /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "chat-message-file-chip",
|
|
onClick: () => openAttachmentFromMessage(rowItem.value_file)
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"),
|
|
/* @__PURE__ */ React.createElement("span", { className: "chat-message-file-name" }, String(rowItem.value_file.file_name || "\u0424\u0430\u0439\u043B"))
|
|
) : /* @__PURE__ */ React.createElement("span", { className: "muted" }, "\u0424\u0430\u0439\u043B \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D") : /* @__PURE__ */ React.createElement("span", { className: "request-data-row-client-text" }, String((rowItem == null ? void 0 : rowItem.field_type) || "").toLowerCase() === "date" ? fmtDateOnly(rowItem == null ? void 0 : rowItem.value_text) : String((rowItem == null ? void 0 : rowItem.value_text) || "").trim().slice(0, 140))) : null
|
|
)) : /* @__PURE__ */ React.createElement("div", { className: "muted" }, "\u041F\u043E\u043B\u044F \u0434\u043B\u044F \u0437\u0430\u043F\u0440\u043E\u0441\u0430 \u0435\u0449\u0435 \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u044B"))), dataRequestModal.error ? /* @__PURE__ */ React.createElement("div", { className: "status error" }, dataRequestModal.error) : null, /* @__PURE__ */ React.createElement("div", { className: "modal-actions modal-actions-right" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "btn btn-sm request-data-submit-btn",
|
|
onClick: submitDataRequestModal,
|
|
disabled: dataRequestModal.loading || dataRequestModal.saving || dataRequestModal.savingTemplate
|
|
},
|
|
dataRequestModal.saving ? "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435..." : "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C"
|
|
)))
|
|
), /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "overlay" + (requestDataListOpen ? " open" : ""),
|
|
onClick: () => setRequestDataListOpen(false),
|
|
"aria-hidden": requestDataListOpen ? "false" : "true"
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "modal request-data-summary-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, "\u0414\u0430\u043D\u043D\u044B\u0435 \u0437\u0430\u044F\u0432\u043A\u0438"), /* @__PURE__ */ React.createElement("p", { className: "muted request-finance-subtitle" }, (row == null ? void 0 : row.track_number) ? "\u0417\u0430\u044F\u0432\u043A\u0430 " + String(row.track_number) : "")), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: () => setRequestDataListOpen(false), "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-list" }, requestDataListItems.length ? requestDataListItems.map((item) => {
|
|
const value = formatRequestDataValue(item);
|
|
const isFile = String((item == null ? void 0 : item.field_type) || "").toLowerCase() === "file";
|
|
return /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-row", key: String(item.id || item.key) }, /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-label" }, String(item.label || humanizeKey(item.key))), /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-value" }, isFile ? value && typeof value === "object" ? /* @__PURE__ */ React.createElement("div", { className: "request-data-summary-file" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "chat-message-file-chip", onClick: () => downloadAttachment(value) }, /* @__PURE__ */ React.createElement("span", { className: "chat-message-file-icon", "aria-hidden": "true" }, "\u{1F4CE}"), /* @__PURE__ */ React.createElement("span", { className: "chat-message-file-name" }, String(value.file_name || "\u0424\u0430\u0439\u043B")))) : /* @__PURE__ */ React.createElement("span", { className: "muted" }, "\u041D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E") : String(value || "\u041D\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E")));
|
|
}) : /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u0414\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043F\u043E \u0437\u0430\u044F\u0432\u043A\u0435 \u043E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044E\u0442")))
|
|
));
|
|
}
|
|
|
|
// app/web/admin/shared/state.js
|
|
function createRequestModalState() {
|
|
return {
|
|
loading: false,
|
|
requestId: null,
|
|
trackNumber: "",
|
|
requestData: null,
|
|
financeSummary: null,
|
|
invoices: [],
|
|
statusRouteNodes: [],
|
|
statusHistory: [],
|
|
availableStatuses: [],
|
|
currentImportantDateAt: "",
|
|
pendingStatusChangePreset: null,
|
|
messages: [],
|
|
attachments: [],
|
|
messageDraft: "",
|
|
selectedFiles: [],
|
|
fileUploading: false
|
|
};
|
|
}
|
|
|
|
// app/web/client.jsx
|
|
(function() {
|
|
const { useCallback, useEffect, useMemo, useRef, useState } = React;
|
|
function StatusLine({ status }) {
|
|
return /* @__PURE__ */ React.createElement("p", { className: "status" + ((status == null ? void 0 : status.kind) ? " " + status.kind : "") }, (status == null ? void 0 : status.message) || "");
|
|
}
|
|
function Overlay({ open, id, onClose, children }) {
|
|
return /* @__PURE__ */ React.createElement("div", { className: "overlay" + (open ? " open" : ""), id, onClick: onClose }, children);
|
|
}
|
|
function GlobalTooltipLayer() {
|
|
const [tooltip, setTooltip] = useState({ open: false, text: "", x: 0, y: 0, maxWidth: 320 });
|
|
const activeRef = useRef(null);
|
|
useEffect(() => {
|
|
const getTarget = (node) => {
|
|
if (!(node instanceof Element)) return null;
|
|
const el = node.closest("[data-tooltip]");
|
|
if (!el) return null;
|
|
const text = String(el.getAttribute("data-tooltip") || "").trim();
|
|
return text ? el : null;
|
|
};
|
|
const reposition = (el) => {
|
|
if (!(el instanceof Element)) return;
|
|
const text = String(el.getAttribute("data-tooltip") || "").trim();
|
|
if (!text) return;
|
|
const rect = el.getBoundingClientRect();
|
|
const vw = window.innerWidth || 0;
|
|
const maxWidth = Math.min(360, Math.max(140, vw - 24));
|
|
const approxWidth = Math.min(maxWidth, Math.max(80, text.length * 7.1 + 22));
|
|
const centerX = rect.left + rect.width / 2;
|
|
const x = Math.max(12 + approxWidth / 2, Math.min(vw - 12 - approxWidth / 2, centerX));
|
|
const y = Math.max(8, rect.top - 8);
|
|
setTooltip({ open: true, text, x, y, maxWidth });
|
|
};
|
|
const open = (node) => {
|
|
const target = getTarget(node);
|
|
if (!target) return;
|
|
activeRef.current = target;
|
|
reposition(target);
|
|
};
|
|
const closeIfNeeded = (related) => {
|
|
const current = activeRef.current;
|
|
if (!current) return;
|
|
if (related instanceof Element) {
|
|
if (related === current || current.contains(related)) return;
|
|
const nextTarget = getTarget(related);
|
|
if (nextTarget === current) return;
|
|
}
|
|
activeRef.current = null;
|
|
setTooltip((prev) => ({ ...prev, open: false }));
|
|
};
|
|
const onMouseOver = (event) => open(event.target);
|
|
const onFocusIn = (event) => open(event.target);
|
|
const onMouseOut = (event) => closeIfNeeded(event.relatedTarget);
|
|
const onFocusOut = (event) => closeIfNeeded(event.relatedTarget);
|
|
const onUpdatePosition = () => {
|
|
if (activeRef.current) reposition(activeRef.current);
|
|
};
|
|
document.addEventListener("mouseover", onMouseOver, true);
|
|
document.addEventListener("focusin", onFocusIn, true);
|
|
document.addEventListener("mouseout", onMouseOut, true);
|
|
document.addEventListener("focusout", onFocusOut, true);
|
|
window.addEventListener("scroll", onUpdatePosition, true);
|
|
window.addEventListener("resize", onUpdatePosition);
|
|
return () => {
|
|
document.removeEventListener("mouseover", onMouseOver, true);
|
|
document.removeEventListener("focusin", onFocusIn, true);
|
|
document.removeEventListener("mouseout", onMouseOut, true);
|
|
document.removeEventListener("focusout", onFocusOut, true);
|
|
window.removeEventListener("scroll", onUpdatePosition, true);
|
|
window.removeEventListener("resize", onUpdatePosition);
|
|
};
|
|
}, []);
|
|
return /* @__PURE__ */ React.createElement(
|
|
"div",
|
|
{
|
|
className: "global-tooltip-layer" + (tooltip.open ? " open" : ""),
|
|
style: { left: tooltip.x + "px", top: tooltip.y + "px", maxWidth: tooltip.maxWidth + "px" },
|
|
role: "tooltip",
|
|
"aria-hidden": tooltip.open ? "false" : "true"
|
|
},
|
|
tooltip.text
|
|
);
|
|
}
|
|
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, "");
|
|
return text.length > 2e5 ? text.slice(0, 2e5) + "\n\n[\u0422\u0435\u043A\u0441\u0442 \u043E\u0431\u0440\u0435\u0437\u0430\u043D \u0434\u043B\u044F \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430]" : text;
|
|
};
|
|
useEffect(() => {
|
|
if (!open || !url) {
|
|
setResolvedUrl("");
|
|
setResolvedText("");
|
|
setResolvedKind("");
|
|
setHint("");
|
|
setLoading(false);
|
|
setError("");
|
|
return;
|
|
}
|
|
const kind2 = detectAttachmentPreviewKind(fileName, mimeType);
|
|
setResolvedKind(kind2);
|
|
setResolvedText("");
|
|
setHint("");
|
|
if (kind2 === "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("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0434\u043B\u044F \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430");
|
|
const buffer = await response.arrayBuffer();
|
|
if (cancelled) return;
|
|
if (kind2 === "pdf") {
|
|
const header = new Uint8Array(buffer.slice(0, 5));
|
|
const isPdf = header.length >= 5 && header[0] === 37 && header[1] === 80 && header[2] === 68 && header[3] === 70 && header[4] === 45;
|
|
if (isPdf) {
|
|
setResolvedUrl(String(url));
|
|
setResolvedKind("pdf");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
const textPreview = decodeTextPreview(buffer);
|
|
if (textPreview != null) {
|
|
setResolvedUrl("");
|
|
setResolvedText(textPreview);
|
|
setResolvedKind("text");
|
|
setHint("\u0424\u0430\u0439\u043B \u043F\u043E\u043C\u0435\u0447\u0435\u043D \u043A\u0430\u043A PDF, \u043D\u043E \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0432\u0430\u043B\u0438\u0434\u043D\u044B\u043C PDF. \u041F\u043E\u043A\u0430\u0437\u0430\u043D \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0439 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
throw new Error("\u0424\u0430\u0439\u043B \u043F\u043E\u043C\u0435\u0447\u0435\u043D \u043A\u0430\u043A PDF, \u043D\u043E \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0432\u0430\u043B\u0438\u0434\u043D\u044B\u043C PDF-\u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u043C.");
|
|
}
|
|
if (kind2 === "text") {
|
|
const textPreview = decodeTextPreview(buffer);
|
|
if (textPreview == null) throw new Error("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0442\u044C \u0442\u0435\u043A\u0441\u0442\u043E\u0432\u044B\u0439 \u0444\u0430\u0439\u043B \u0434\u043B\u044F \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430.");
|
|
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(kind2);
|
|
setLoading(false);
|
|
} catch (err) {
|
|
if (cancelled) return;
|
|
setError(err instanceof Error ? err.message : "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440");
|
|
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 /* @__PURE__ */ React.createElement(Overlay, { open, id: "file-preview-overlay", onClose: (event) => event.target.id === "file-preview-overlay" && onClose() }, /* @__PURE__ */ React.createElement("div", { className: "modal request-preview-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("h3", null, title || fileName || "\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0444\u0430\u0439\u043B\u0430"), /* @__PURE__ */ React.createElement("div", { className: "request-preview-head-actions" }, /* @__PURE__ */ React.createElement("a", { className: "icon-btn file-action-btn request-preview-download-icon", href: url, target: "_blank", rel: "noreferrer", "aria-label": "\u0421\u043A\u0430\u0447\u0430\u0442\u044C \u0444\u0430\u0439\u043B", "data-tooltip": "\u0421\u043A\u0430\u0447\u0430\u0442\u044C" }, /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M12 3a1 1 0 0 1 1 1v8.17l2.58-2.58a1 1 0 1 1 1.42 1.42l-4.3 4.3a1 1 0 0 1-1.4 0l-4.3-4.3a1 1 0 0 1 1.42-1.42L11 12.17V4a1 1 0 0 1 1-1zm-7 14a1 1 0 0 1 1 1v1h12v-1a1 1 0 1 1 2 0v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z",
|
|
fill: "currentColor"
|
|
}
|
|
))), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", id: "file-preview-close", onClick: onClose }, "\xD7"))), /* @__PURE__ */ React.createElement("div", { className: "request-preview-body", id: "file-preview-body" }, loading ? /* @__PURE__ */ React.createElement("p", { className: "request-preview-note" }, "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430...") : null, !loading && !error && hint ? /* @__PURE__ */ React.createElement("p", { className: "request-preview-note" }, hint) : null, error ? /* @__PURE__ */ React.createElement("p", { className: "request-preview-note" }, error) : null, !loading && !error && kind === "image" && resolvedUrl ? /* @__PURE__ */ React.createElement("img", { className: "request-preview-image", src: resolvedUrl, alt: fileName || "attachment" }) : null, !loading && !error && kind === "video" && resolvedUrl ? /* @__PURE__ */ React.createElement("video", { className: "request-preview-video", src: resolvedUrl, controls: true, preload: "metadata" }) : null, !loading && !error && kind === "pdf" && resolvedUrl ? /* @__PURE__ */ React.createElement("iframe", { className: "request-preview-frame", src: resolvedUrl, title: fileName || "preview" }) : null, !loading && !error && kind === "text" ? /* @__PURE__ */ React.createElement("pre", { className: "request-preview-text" }, resolvedText || "\u0424\u0430\u0439\u043B \u043F\u0443\u0441\u0442.") : null, kind === "none" ? /* @__PURE__ */ React.createElement("p", { className: "request-preview-note" }, "\u0414\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0442\u0438\u043F\u0430 \u0444\u0430\u0439\u043B\u0430 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043E\u0442\u043A\u0440\u044B\u0442\u0438\u0435 \u0438\u043B\u0438 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u0435.") : null)));
|
|
}
|
|
function ClientHelpModal({
|
|
open,
|
|
status,
|
|
loadingType,
|
|
lawyerChangeReason,
|
|
curatorBlocked,
|
|
lawyerChangeBlocked,
|
|
onClose,
|
|
onReasonChange,
|
|
onSubmitCurator,
|
|
onSubmitLawyerChange
|
|
}) {
|
|
return /* @__PURE__ */ React.createElement("div", { className: "overlay" + (open ? " open" : ""), id: "client-help-overlay", onClick: (event) => event.target.id === "client-help-overlay" && onClose() }, /* @__PURE__ */ React.createElement("div", { className: "modal client-help-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", { id: "client-help-title" }, "\u041F\u043E\u043C\u043E\u0449\u044C \u043F\u043E \u0437\u0430\u044F\u0432\u043A\u0435")), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", id: "client-help-close", onClick: onClose, "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7")), /* @__PURE__ */ React.createElement("div", { className: "client-help-stack" }, /* @__PURE__ */ React.createElement("section", { className: "client-help-block" }, /* @__PURE__ */ React.createElement("p", { className: "client-help-description" }, "\u0415\u0441\u043B\u0438 \u043D\u0443\u0436\u043D\u0430 \u0434\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 \u043F\u043E \u0434\u0435\u043B\u0443, \u043C\u043E\u0436\u043D\u043E \u043E\u0431\u0440\u0430\u0442\u0438\u0442\u044C\u0441\u044F \u043A \u043A\u0443\u0440\u0430\u0442\u043E\u0440\u0443. \u0417\u0430\u043F\u0440\u043E\u0441 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0443 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u044B."), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "btn secondary btn-sm",
|
|
id: "cabinet-curator-request-open",
|
|
type: "button",
|
|
disabled: curatorBlocked || loadingType === "CURATOR_CONTACT",
|
|
onClick: onSubmitCurator
|
|
},
|
|
loadingType === "CURATOR_CONTACT" ? "\u041E\u0442\u043F\u0440\u0430\u0432\u043A\u0430..." : "\u041E\u0431\u0440\u0430\u0442\u0438\u0442\u044C\u0441\u044F \u043A \u043A\u0443\u0440\u0430\u0442\u043E\u0440\u0443"
|
|
)), /* @__PURE__ */ React.createElement("section", { className: "client-help-block" }, /* @__PURE__ */ React.createElement("p", { className: "client-help-description" }, "\u0415\u0441\u043B\u0438 \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u044E\u0440\u0438\u0441\u0442 \u043D\u0435 \u0443\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442, \u043C\u043E\u0436\u043D\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0435\u0433\u043E \u0441\u043C\u0435\u043D\u0443. \u0423\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0434\u043B\u044F \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430."), /* @__PURE__ */ React.createElement("div", { className: "field" }, /* @__PURE__ */ React.createElement("label", { htmlFor: "service-request-body" }, "\u041F\u0440\u0438\u0447\u0438\u043D\u0430 \u0441\u043C\u0435\u043D\u044B \u044E\u0440\u0438\u0441\u0442\u0430"), /* @__PURE__ */ React.createElement(
|
|
"textarea",
|
|
{
|
|
id: "service-request-body",
|
|
value: lawyerChangeReason,
|
|
onChange: onReasonChange,
|
|
maxLength: 4e3,
|
|
placeholder: "\u041E\u043F\u0438\u0448\u0438\u0442\u0435 \u043F\u0440\u0438\u0447\u0438\u043D\u0443",
|
|
disabled: lawyerChangeBlocked || loadingType === "LAWYER_CHANGE_REQUEST"
|
|
}
|
|
)), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "btn secondary btn-sm",
|
|
id: "cabinet-lawyer-change-open",
|
|
type: "button",
|
|
disabled: lawyerChangeBlocked || loadingType === "LAWYER_CHANGE_REQUEST",
|
|
onClick: onSubmitLawyerChange
|
|
},
|
|
loadingType === "LAWYER_CHANGE_REQUEST" ? "\u041E\u0442\u043F\u0440\u0430\u0432\u043A\u0430..." : "\u0417\u0430\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0441\u043C\u0435\u043D\u0443"
|
|
)), /* @__PURE__ */ React.createElement(StatusLine, { status }))));
|
|
}
|
|
function RequestPickerModal({
|
|
open,
|
|
loading,
|
|
requests,
|
|
activeTrack,
|
|
onClose,
|
|
onReload,
|
|
onSelect
|
|
}) {
|
|
const rows = Array.isArray(requests) ? requests : [];
|
|
return /* @__PURE__ */ React.createElement(Overlay, { open, id: "client-request-picker-overlay", onClose: (event) => event.target.id === "client-request-picker-overlay" && onClose() }, /* @__PURE__ */ React.createElement("div", { className: "modal client-request-picker-modal", onClick: (event) => event.stopPropagation() }, /* @__PURE__ */ React.createElement("div", { className: "modal-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h3", null, "\u0412\u044B\u0431\u043E\u0440 \u0437\u0430\u044F\u0432\u043A\u0438"), /* @__PURE__ */ React.createElement("p", { className: "muted client-request-picker-subtitle" }, "\u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u043D\u0443\u0436\u043D\u0443\u044E \u0437\u0430\u044F\u0432\u043A\u0443 \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430")), /* @__PURE__ */ React.createElement("div", { className: "modal-head-actions" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "icon-btn file-action-btn",
|
|
type: "button",
|
|
"data-tooltip": "\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A",
|
|
"aria-label": "\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A",
|
|
onClick: onReload,
|
|
disabled: loading
|
|
},
|
|
/* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 24 24", width: "16", height: "16", "aria-hidden": "true", focusable: "false" }, /* @__PURE__ */ React.createElement(
|
|
"path",
|
|
{
|
|
d: "M12 4a8 8 0 0 1 7.7 5.8 1 1 0 0 1-1.92.56A6 6 0 1 0 16.7 16H15a1 1 0 1 1 0-2h4a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0v-1.18A8 8 0 1 1 12 4z",
|
|
fill: "currentColor"
|
|
}
|
|
))
|
|
), /* @__PURE__ */ React.createElement("button", { className: "close", type: "button", onClick: onClose, "aria-label": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C" }, "\xD7"))), /* @__PURE__ */ React.createElement("ul", { className: "simple-list client-request-picker-list", id: "client-request-picker-list" }, rows.length ? rows.map((row, index) => {
|
|
const track = String((row == null ? void 0 : row.track_number) || "").trim();
|
|
const isActive = track && track === String(activeTrack || "").trim();
|
|
const hasUpdates = Number((row == null ? void 0 : row.viewer_unread_total) || 0) > 0 || Boolean(row == null ? void 0 : row.client_has_unread_updates);
|
|
const statusName = String((row == null ? void 0 : row.status_name) || statusLabel(row == null ? void 0 : row.status_code) || "-");
|
|
return /* @__PURE__ */ React.createElement(
|
|
"li",
|
|
{
|
|
key: String((row == null ? void 0 : row.id) || track || "row-" + String(index)),
|
|
className: "client-request-picker-item" + (isActive ? " active" : "") + (hasUpdates ? " has-updates" : "")
|
|
},
|
|
/* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
type: "button",
|
|
className: "client-request-picker-btn",
|
|
onClick: () => onSelect(track),
|
|
disabled: !track || loading,
|
|
"aria-label": "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443 " + (track || "")
|
|
},
|
|
/* @__PURE__ */ React.createElement("div", { className: "client-request-picker-head" }, /* @__PURE__ */ React.createElement("span", { className: "client-request-picker-track" }, track || "\u0411\u0435\u0437 \u043D\u043E\u043C\u0435\u0440\u0430"), /* @__PURE__ */ React.createElement("span", { className: "client-request-news-dot" + (hasUpdates ? " active" : ""), "aria-hidden": "true" })),
|
|
/* @__PURE__ */ React.createElement("div", { className: "client-request-picker-meta" }, /* @__PURE__ */ React.createElement("span", { className: "client-request-picker-status" }, statusName), /* @__PURE__ */ React.createElement("span", { className: "client-request-picker-updated" }, fmtShortDateTime(row == null ? void 0 : row.updated_at)))
|
|
)
|
|
);
|
|
}) : /* @__PURE__ */ React.createElement("li", { className: "muted" }, loading ? "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u043F\u0438\u0441\u043A\u0430..." : "\u0417\u0430\u044F\u0432\u043E\u043A \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E")), /* @__PURE__ */ React.createElement("div", { className: "client-request-picker-actions" }, /* @__PURE__ */ React.createElement("button", { className: "btn secondary btn-sm", type: "button", onClick: onClose }, "\u041E\u0442\u043C\u0435\u043D\u0430"))));
|
|
}
|
|
function App() {
|
|
var _a, _b;
|
|
const UPLOAD_MAX_ATTEMPTS = 4;
|
|
const [requestModal, setRequestModal] = useState(createRequestModalState());
|
|
const [requestsList, setRequestsList] = useState([]);
|
|
const [activeTrack, setActiveTrack] = useState("");
|
|
const [requestPickerModal, setRequestPickerModal] = useState({ open: false, loading: false });
|
|
const [status, setStatus] = useState({ message: "", kind: "" });
|
|
const [serviceRequests, setServiceRequests] = useState([]);
|
|
const [clientHelpModal, setClientHelpModal] = useState({
|
|
open: false,
|
|
loadingType: "",
|
|
lawyerChangeReason: "",
|
|
status: { message: "", kind: "" }
|
|
});
|
|
const setPageStatus = useCallback((message, kind) => {
|
|
setStatus({ message: String(message || ""), kind: kind || "" });
|
|
}, []);
|
|
const apiError = (data, fallback) => {
|
|
if (data && typeof data.detail === "string" && data.detail.trim()) return data.detail;
|
|
return fallback;
|
|
};
|
|
const parseJsonSafe = async (response) => {
|
|
try {
|
|
return await response.json();
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
};
|
|
const apiJson = useCallback(async (url, options, fallbackMessage) => {
|
|
const response = await fetch(url, options || void 0);
|
|
const data = await parseJsonSafe(response);
|
|
if (response.status === 401 || response.status === 403) {
|
|
window.location.href = "/";
|
|
throw new Error("\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430");
|
|
}
|
|
if (!response.ok) {
|
|
const error = new Error(apiError(data, fallbackMessage || "\u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u0430"));
|
|
error.httpStatus = Number(response.status || 0);
|
|
throw error;
|
|
}
|
|
return data;
|
|
}, []);
|
|
const buildStorageUploadError = useCallback(async (response, fallbackMessage) => {
|
|
const base = String(fallbackMessage || "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0444\u0430\u0439\u043B\u0430 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435");
|
|
const status2 = Number((response == null ? void 0 : response.status) || 0);
|
|
const statusText = String((response == null ? void 0 : response.statusText) || "").trim();
|
|
let details = "";
|
|
try {
|
|
details = String(await response.text() || "").replace(/\s+/g, " ").trim();
|
|
} catch (_) {
|
|
details = "";
|
|
}
|
|
if (details.length > 180) details = details.slice(0, 180) + "...";
|
|
const parts = [];
|
|
if (status2 > 0) parts.push("HTTP " + status2 + (statusText ? " " + statusText : ""));
|
|
if (details) parts.push(details);
|
|
return parts.length ? base + " (" + parts.join("; ") + ")" : base;
|
|
}, []);
|
|
const wait = useCallback(async (ms) => {
|
|
await new Promise((resolve) => window.setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
}, []);
|
|
const nextUploadRetryDelayMs = useCallback((attempt) => {
|
|
const base = Math.min(1200 * Math.pow(2, Math.max(0, Number(attempt || 1) - 1)), 7e3);
|
|
const jitter = Math.floor(Math.random() * 250);
|
|
return base + jitter;
|
|
}, []);
|
|
const isRetryableUploadError = useCallback((error) => {
|
|
const status2 = Number((error == null ? void 0 : error.httpStatus) || (error == null ? void 0 : error.status) || 0);
|
|
if ([408, 425, 429, 500, 502, 503, 504].includes(status2)) return true;
|
|
if (status2 > 0) return false;
|
|
const message = String((error == null ? void 0 : error.message) || "").toLowerCase();
|
|
if (!message) return true;
|
|
return message.includes("networkerror") || message.includes("failed to fetch") || message.includes("load failed") || message.includes("network request failed") || message.includes("timeout");
|
|
}, []);
|
|
const runUploadStepWithRetry = useCallback(
|
|
async (label, action) => {
|
|
let lastError = null;
|
|
let attemptsUsed = 0;
|
|
for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt += 1) {
|
|
attemptsUsed = attempt;
|
|
try {
|
|
return await action(attempt);
|
|
} catch (error) {
|
|
lastError = error;
|
|
const canRetry = attempt < UPLOAD_MAX_ATTEMPTS && isRetryableUploadError(error);
|
|
if (!canRetry) break;
|
|
await wait(nextUploadRetryDelayMs(attempt));
|
|
}
|
|
}
|
|
const reason = String((lastError == null ? void 0 : lastError.message) || "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0442\u0438");
|
|
throw new Error(label + ": " + reason + " (\u043F\u043E\u043F\u044B\u0442\u043E\u043A: " + attemptsUsed + ")");
|
|
},
|
|
[UPLOAD_MAX_ATTEMPTS, isRetryableUploadError, nextUploadRetryDelayMs, wait]
|
|
);
|
|
const uploadPublicRequestAttachment = useCallback(async (file, extra = {}) => {
|
|
const requestId = String(requestModal.requestId || "").trim();
|
|
if (!requestId) throw new Error("\u041D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u0430 \u0437\u0430\u044F\u0432\u043A\u0430");
|
|
const mimeType = String((file == null ? void 0 : file.type) || "application/octet-stream");
|
|
const initData = await runUploadStepWithRetry("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0447\u0430\u0442\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443 \u0444\u0430\u0439\u043B\u0430", async () => {
|
|
return apiJson(
|
|
"/api/public/uploads/init",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
file_name: file.name,
|
|
mime_type: mimeType,
|
|
size_bytes: file.size,
|
|
scope: "REQUEST_ATTACHMENT",
|
|
request_id: requestId
|
|
})
|
|
},
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0447\u0430\u0442\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443 \u0444\u0430\u0439\u043B\u0430"
|
|
);
|
|
});
|
|
await runUploadStepWithRetry("\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0444\u0430\u0439\u043B\u0430 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", async () => {
|
|
const putResponse = await fetch(initData.presigned_url, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": mimeType },
|
|
body: file
|
|
});
|
|
if (putResponse.ok) return null;
|
|
const error = new Error(await buildStorageUploadError(putResponse, "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0444\u0430\u0439\u043B\u0430 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435"));
|
|
error.httpStatus = Number(putResponse.status || 0);
|
|
throw error;
|
|
});
|
|
const completeData = await runUploadStepWithRetry("\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443 \u0444\u0430\u0439\u043B\u0430", async () => {
|
|
return apiJson(
|
|
"/api/public/uploads/complete",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
key: initData.key,
|
|
file_name: file.name,
|
|
mime_type: mimeType,
|
|
size_bytes: file.size,
|
|
scope: "REQUEST_ATTACHMENT",
|
|
request_id: requestId,
|
|
message_id: (extra == null ? void 0 : extra.message_id) || null
|
|
})
|
|
},
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0443 \u0444\u0430\u0439\u043B\u0430"
|
|
);
|
|
});
|
|
return completeData;
|
|
}, [apiJson, buildStorageUploadError, requestModal.requestId, runUploadStepWithRetry]);
|
|
const loadRequestWorkspace = useCallback(
|
|
async (trackNumber, showLoading) => {
|
|
const track = String(trackNumber || "").trim().toUpperCase();
|
|
if (!track) return;
|
|
if (showLoading) {
|
|
setRequestModal((prev) => ({ ...prev, loading: true }));
|
|
}
|
|
const [requestData, messagesData, attachmentsData, invoicesData, statusRouteData, serviceRequestsData] = await Promise.all([
|
|
apiJson("/api/public/requests/" + encodeURIComponent(track), null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443"),
|
|
apiJson("/api/public/chat/requests/" + encodeURIComponent(track) + "/messages", null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F"),
|
|
apiJson("/api/public/requests/" + encodeURIComponent(track) + "/attachments", null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B\u044B"),
|
|
apiJson("/api/public/requests/" + encodeURIComponent(track) + "/invoices", null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0447\u0435\u0442\u0430"),
|
|
apiJson("/api/public/requests/" + encodeURIComponent(track) + "/status-route", null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043C\u0430\u0440\u0448\u0440\u0443\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u043E\u0432"),
|
|
apiJson("/api/public/requests/" + encodeURIComponent(track) + "/service-requests", null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u044F")
|
|
]);
|
|
const invoices = Array.isArray(invoicesData) ? invoicesData : [];
|
|
const paidInvoices = invoices.filter((item) => String((item == null ? void 0 : item.status) || "").toUpperCase() === "PAID");
|
|
const paidTotal = paidInvoices.reduce((acc, item) => {
|
|
const amount = Number((item == null ? void 0 : item.amount) || 0);
|
|
return Number.isFinite(amount) ? acc + amount : acc;
|
|
}, 0);
|
|
const lastPaidAt = paidInvoices.reduce((latest, item) => {
|
|
const raw = String((item == null ? void 0 : item.paid_at) || "").trim();
|
|
if (!raw) return latest;
|
|
if (!latest) return raw;
|
|
const currentTs = new Date(raw).getTime();
|
|
const latestTs = new Date(latest).getTime();
|
|
return Number.isFinite(currentTs) && currentTs > latestTs ? raw : latest;
|
|
}, "");
|
|
setActiveTrack(track);
|
|
setServiceRequests(Array.isArray(serviceRequestsData) ? serviceRequestsData : []);
|
|
setRequestModal((prev) => {
|
|
var _a2, _b2;
|
|
return {
|
|
...prev,
|
|
loading: false,
|
|
requestId: String((requestData == null ? void 0 : requestData.id) || ""),
|
|
trackNumber: String((requestData == null ? void 0 : requestData.track_number) || track),
|
|
requestData: requestData || null,
|
|
financeSummary: {
|
|
request_cost: (_a2 = requestData == null ? void 0 : requestData.request_cost) != null ? _a2 : null,
|
|
effective_rate: (_b2 = requestData == null ? void 0 : requestData.effective_rate) != null ? _b2 : null,
|
|
paid_total: Math.round((paidTotal + Number.EPSILON) * 100) / 100,
|
|
last_paid_at: lastPaidAt || (requestData == null ? void 0 : requestData.paid_at) || null
|
|
},
|
|
statusRouteNodes: Array.isArray(statusRouteData == null ? void 0 : statusRouteData.nodes) ? statusRouteData.nodes : [],
|
|
statusHistory: Array.isArray(statusRouteData == null ? void 0 : statusRouteData.history) ? statusRouteData.history : [],
|
|
availableStatuses: [],
|
|
currentImportantDateAt: String((statusRouteData == null ? void 0 : statusRouteData.current_important_date_at) || (requestData == null ? void 0 : requestData.important_date_at) || ""),
|
|
invoices,
|
|
messages: Array.isArray(messagesData) ? messagesData : [],
|
|
attachments: Array.isArray(attachmentsData) ? attachmentsData : [],
|
|
fileUploading: false
|
|
};
|
|
});
|
|
},
|
|
[apiJson]
|
|
);
|
|
const refreshRequestsList = useCallback(async () => {
|
|
const data = await apiJson("/api/public/requests/my", null, "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u0430\u044F\u0432\u043E\u043A");
|
|
const rows = Array.isArray(data == null ? void 0 : data.rows) ? data.rows : [];
|
|
setRequestsList(rows);
|
|
return rows;
|
|
}, [apiJson]);
|
|
const loadMyRequests = useCallback(
|
|
async (preferredTrack) => {
|
|
const rows = await refreshRequestsList();
|
|
if (!rows.length) {
|
|
setRequestModal((prev) => ({
|
|
...prev,
|
|
loading: false,
|
|
requestId: null,
|
|
requestData: null,
|
|
trackNumber: "",
|
|
financeSummary: null,
|
|
invoices: [],
|
|
statusRouteNodes: [],
|
|
statusHistory: [],
|
|
messages: [],
|
|
attachments: [],
|
|
fileUploading: false,
|
|
selectedFiles: [],
|
|
messageDraft: ""
|
|
}));
|
|
setServiceRequests([]);
|
|
setPageStatus("\u041F\u043E \u0432\u0430\u0448\u0435\u043C\u0443 \u043D\u043E\u043C\u0435\u0440\u0443 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u0437\u0430\u044F\u0432\u043E\u043A.", "");
|
|
return;
|
|
}
|
|
const tracks = rows.map((row) => String(row.track_number || "").trim()).filter(Boolean);
|
|
const selected = tracks.includes(String(preferredTrack || "").trim().toUpperCase()) ? String(preferredTrack || "").trim().toUpperCase() : tracks[0];
|
|
await loadRequestWorkspace(selected, true);
|
|
},
|
|
[loadRequestWorkspace, refreshRequestsList, setPageStatus]
|
|
);
|
|
const openRequestPicker = useCallback(() => {
|
|
setRequestPickerModal({ open: true, loading: true });
|
|
void refreshRequestsList().catch((error) => setPageStatus((error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u0430\u044F\u0432\u043E\u043A", "error")).finally(() => {
|
|
setRequestPickerModal((prev) => ({ ...prev, loading: false }));
|
|
});
|
|
}, [refreshRequestsList, setPageStatus]);
|
|
const closeRequestPicker = useCallback(() => {
|
|
setRequestPickerModal((prev) => ({ ...prev, open: false }));
|
|
}, []);
|
|
const reloadRequestPicker = useCallback(() => {
|
|
setRequestPickerModal((prev) => ({ ...prev, loading: true }));
|
|
void refreshRequestsList().catch((error) => setPageStatus((error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u0430\u044F\u0432\u043E\u043A", "error")).finally(() => {
|
|
setRequestPickerModal((prev) => ({ ...prev, loading: false }));
|
|
});
|
|
}, [refreshRequestsList, setPageStatus]);
|
|
const selectRequestFromPicker = useCallback(
|
|
async (trackNumber) => {
|
|
const track = String(trackNumber || "").trim().toUpperCase();
|
|
if (!track) return;
|
|
setRequestPickerModal((prev) => ({ ...prev, loading: true }));
|
|
try {
|
|
await loadRequestWorkspace(track, true);
|
|
await refreshRequestsList();
|
|
setRequestPickerModal({ open: false, loading: false });
|
|
} catch (error) {
|
|
setRequestPickerModal((prev) => ({ ...prev, loading: false }));
|
|
setPageStatus((error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0437\u0430\u044F\u0432\u043A\u0443", "error");
|
|
}
|
|
},
|
|
[loadRequestWorkspace, refreshRequestsList, setPageStatus]
|
|
);
|
|
const updateMessageDraft = useCallback((event) => {
|
|
var _a2;
|
|
const value = ((_a2 = event == null ? void 0 : event.target) == null ? void 0 : _a2.value) || "";
|
|
setRequestModal((prev) => ({ ...prev, messageDraft: value }));
|
|
}, []);
|
|
const appendFiles = useCallback((files) => {
|
|
const list = Array.isArray(files) ? files.filter(Boolean) : [];
|
|
if (!list.length) return;
|
|
setRequestModal((prev) => {
|
|
const existing = Array.isArray(prev.selectedFiles) ? prev.selectedFiles : [];
|
|
const next = [...existing];
|
|
list.forEach((file) => {
|
|
const duplicate = next.some(
|
|
(item) => item && item.name === file.name && Number(item.size || 0) === Number(file.size || 0) && Number(item.lastModified || 0) === Number(file.lastModified || 0)
|
|
);
|
|
if (!duplicate) next.push(file);
|
|
});
|
|
return { ...prev, selectedFiles: next };
|
|
});
|
|
}, []);
|
|
const removeFile = useCallback((index) => {
|
|
setRequestModal((prev) => {
|
|
const files = Array.isArray(prev.selectedFiles) ? [...prev.selectedFiles] : [];
|
|
files.splice(index, 1);
|
|
return { ...prev, selectedFiles: files };
|
|
});
|
|
}, []);
|
|
const clearFiles = useCallback(() => {
|
|
setRequestModal((prev) => ({ ...prev, selectedFiles: [] }));
|
|
}, []);
|
|
const downloadPublicInvoicePdf = useCallback(
|
|
async (row) => {
|
|
const url = String((row == null ? void 0 : row.download_url) || "").trim();
|
|
if (!url) {
|
|
setPageStatus("\u0421\u0441\u044B\u043B\u043A\u0430 \u043D\u0430 PDF \u0441\u0447\u0435\u0442\u0430 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0430.", "error");
|
|
return;
|
|
}
|
|
try {
|
|
setPageStatus("\u0421\u043A\u0430\u0447\u0438\u0432\u0430\u0435\u043C PDF...", "");
|
|
const response = await fetch(url, { credentials: "same-origin" });
|
|
if (!response.ok) {
|
|
const text = await response.text();
|
|
let payload = {};
|
|
try {
|
|
payload = text ? JSON.parse(text) : {};
|
|
} catch (_) {
|
|
payload = { raw: text };
|
|
}
|
|
const message = payload.detail || payload.error || payload.raw || "HTTP " + response.status;
|
|
throw new Error(String(message));
|
|
}
|
|
const blob = await response.blob();
|
|
const fileName = String((row == null ? void 0 : row.invoice_number) || "invoice") + ".pdf";
|
|
const fileUrl = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = fileUrl;
|
|
link.download = fileName;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
URL.revokeObjectURL(fileUrl);
|
|
setPageStatus("PDF \u0441\u043A\u0430\u0447\u0430\u043D", "ok");
|
|
} catch (error) {
|
|
setPageStatus("\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F: " + ((error == null ? void 0 : error.message) || "\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430"), "error");
|
|
}
|
|
},
|
|
[setPageStatus]
|
|
);
|
|
const submitMessage = useCallback(
|
|
async (event) => {
|
|
if (event && typeof event.preventDefault === "function") event.preventDefault();
|
|
const track = String(activeTrack || "").trim();
|
|
const requestId = String(requestModal.requestId || "").trim();
|
|
if (!track || !requestId) {
|
|
setPageStatus("\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u044F\u0432\u043A\u0443.", "error");
|
|
return;
|
|
}
|
|
const body = String(requestModal.messageDraft || "").trim();
|
|
const files = Array.isArray(requestModal.selectedFiles) ? requestModal.selectedFiles : [];
|
|
if (!body && !files.length) return;
|
|
try {
|
|
setRequestModal((prev) => ({ ...prev, fileUploading: true }));
|
|
setPageStatus(files.length ? "\u041E\u0442\u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u0438 \u0444\u0430\u0439\u043B\u043E\u0432..." : "\u041E\u0442\u043F\u0440\u0430\u0432\u043A\u0430 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F...", "");
|
|
let messageId = null;
|
|
if (body) {
|
|
const messageData = await apiJson(
|
|
"/api/public/chat/requests/" + encodeURIComponent(track) + "/messages",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ body })
|
|
},
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435"
|
|
);
|
|
messageId = String((messageData == null ? void 0 : messageData.id) || "").trim() || null;
|
|
}
|
|
for (const file of files) {
|
|
await uploadPublicRequestAttachment(file, { message_id: messageId });
|
|
}
|
|
setRequestModal((prev) => ({ ...prev, messageDraft: "", selectedFiles: [], fileUploading: false }));
|
|
await loadRequestWorkspace(track, false);
|
|
if (body && files.length) setPageStatus("\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0438 \u0444\u0430\u0439\u043B\u044B \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u044B.", "ok");
|
|
else if (files.length) setPageStatus(files.length === 1 ? "\u0424\u0430\u0439\u043B \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D." : "\u0424\u0430\u0439\u043B\u044B \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u044B.", "ok");
|
|
else setPageStatus("\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E.", "ok");
|
|
} catch (error) {
|
|
setRequestModal((prev) => ({ ...prev, fileUploading: false }));
|
|
setPageStatus((error == null ? void 0 : error.message) || "\u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F", "error");
|
|
}
|
|
},
|
|
[activeTrack, apiJson, loadRequestWorkspace, requestModal.messageDraft, requestModal.requestId, requestModal.selectedFiles, setPageStatus, uploadPublicRequestAttachment]
|
|
);
|
|
const loadRequestDataBatch = useCallback(
|
|
async (messageId) => {
|
|
const track = String(activeTrack || "").trim();
|
|
if (!track || !messageId) throw new Error("\u041D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u0430 \u0437\u0430\u044F\u0432\u043A\u0430");
|
|
return apiJson(
|
|
"/api/public/chat/requests/" + encodeURIComponent(track) + "/data-requests/" + encodeURIComponent(String(messageId)),
|
|
null,
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u0430\u043D\u043D\u044B\u0445"
|
|
);
|
|
},
|
|
[activeTrack, apiJson]
|
|
);
|
|
const saveRequestDataValues = useCallback(
|
|
async ({ message_id, items }) => {
|
|
const track = String(activeTrack || "").trim();
|
|
const messageId = String(message_id || "").trim();
|
|
if (!track || !messageId) throw new Error("\u041D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u0430 \u0437\u0430\u044F\u0432\u043A\u0430");
|
|
await apiJson(
|
|
"/api/public/chat/requests/" + encodeURIComponent(track) + "/data-requests/" + encodeURIComponent(messageId),
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ items: Array.isArray(items) ? items : [] })
|
|
},
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0434\u0430\u043D\u043D\u044B\u0435"
|
|
);
|
|
await loadRequestWorkspace(track, false);
|
|
},
|
|
[activeTrack, apiJson, loadRequestWorkspace]
|
|
);
|
|
const probeLiveState = useCallback(
|
|
async ({ cursor } = {}) => {
|
|
const track = String(activeTrack || "").trim();
|
|
if (!track) return { has_updates: false, typing: [], cursor: null };
|
|
const query = cursor ? "?cursor=" + encodeURIComponent(String(cursor)) : "";
|
|
const payload = await apiJson(
|
|
"/api/public/chat/requests/" + encodeURIComponent(track) + "/live" + query,
|
|
null,
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C live-\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u0447\u0430\u0442\u0430"
|
|
);
|
|
if (payload && payload.has_updates) {
|
|
await loadRequestWorkspace(track, false);
|
|
}
|
|
return payload || { has_updates: false, typing: [], cursor: null };
|
|
},
|
|
[activeTrack, apiJson, loadRequestWorkspace]
|
|
);
|
|
const setTypingSignal = useCallback(
|
|
async ({ typing } = {}) => {
|
|
const track = String(activeTrack || "").trim();
|
|
if (!track) return { status: "skipped", typing: false };
|
|
return apiJson(
|
|
"/api/public/chat/requests/" + encodeURIComponent(track) + "/typing",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ typing: Boolean(typing) })
|
|
},
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0442\u0430\u0442\u0443\u0441 \u043D\u0430\u0431\u043E\u0440\u0430"
|
|
);
|
|
},
|
|
[activeTrack, apiJson]
|
|
);
|
|
const openClientHelpModal = useCallback(() => {
|
|
setClientHelpModal((prev) => ({
|
|
...prev,
|
|
open: true,
|
|
status: { message: "", kind: "" }
|
|
}));
|
|
}, []);
|
|
const closeClientHelpModal = useCallback(() => {
|
|
setClientHelpModal((prev) => ({
|
|
...prev,
|
|
open: false,
|
|
loadingType: "",
|
|
status: { message: "", kind: "" }
|
|
}));
|
|
}, []);
|
|
const normalizedCurrentLawyerId = String(((_a = requestModal.requestData) == null ? void 0 : _a.assigned_lawyer_id) || "").trim().toLowerCase();
|
|
const serviceRequestState = useMemo(() => {
|
|
const rows = Array.isArray(serviceRequests) ? serviceRequests : [];
|
|
const curatorRows = rows.filter((item) => String((item == null ? void 0 : item.type) || "").toUpperCase() === "CURATOR_CONTACT");
|
|
const lawyerRows = rows.filter((item) => String((item == null ? void 0 : item.type) || "").toUpperCase() === "LAWYER_CHANGE_REQUEST");
|
|
const latestLawyerChange = lawyerRows[0] || null;
|
|
const hasCuratorRequest = curatorRows.length > 0;
|
|
let lawyerChangeDisabledByState = false;
|
|
if (latestLawyerChange) {
|
|
const hasLawyerSnapshot = Object.prototype.hasOwnProperty.call(latestLawyerChange, "assigned_lawyer_id");
|
|
if (hasLawyerSnapshot) {
|
|
const requestedForLawyer = String((latestLawyerChange == null ? void 0 : latestLawyerChange.assigned_lawyer_id) || "").trim().toLowerCase();
|
|
lawyerChangeDisabledByState = requestedForLawyer === normalizedCurrentLawyerId;
|
|
} else {
|
|
const statusCode = String((latestLawyerChange == null ? void 0 : latestLawyerChange.status) || "").toUpperCase();
|
|
lawyerChangeDisabledByState = statusCode === "NEW" || statusCode === "IN_PROGRESS";
|
|
}
|
|
}
|
|
return {
|
|
hasCuratorRequest,
|
|
lawyerChangeDisabledByState
|
|
};
|
|
}, [normalizedCurrentLawyerId, serviceRequests]);
|
|
const canInteract = Boolean(requestModal.requestData && !requestModal.loading);
|
|
const curatorBlocked = !canInteract || serviceRequestState.hasCuratorRequest;
|
|
const lawyerChangeBlocked = !canInteract || serviceRequestState.lawyerChangeDisabledByState;
|
|
const submitServiceRequest = useCallback(
|
|
async ({ type, body }) => {
|
|
const track = String(activeTrack || "").trim();
|
|
const requestType = String(type || "").trim().toUpperCase();
|
|
const message = String(body || "").trim();
|
|
if (!track) {
|
|
setClientHelpModal((prev) => ({ ...prev, status: { message: "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u044F\u0432\u043A\u0443.", kind: "error" } }));
|
|
return;
|
|
}
|
|
if (!requestType) {
|
|
setClientHelpModal((prev) => ({ ...prev, status: { message: "\u041D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u0442\u0438\u043F \u0437\u0430\u043F\u0440\u043E\u0441\u0430.", kind: "error" } }));
|
|
return;
|
|
}
|
|
if (message.length < 3) {
|
|
setClientHelpModal((prev) => ({ ...prev, status: { message: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u043C\u0438\u043D\u0438\u043C\u0443\u043C 3 \u0441\u0438\u043C\u0432\u043E\u043B\u0430.", kind: "error" } }));
|
|
return;
|
|
}
|
|
try {
|
|
setClientHelpModal((prev) => ({ ...prev, loadingType: requestType, status: { message: "", kind: "" } }));
|
|
await apiJson(
|
|
"/api/public/requests/" + encodeURIComponent(track) + "/service-requests",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ type: requestType, body: message })
|
|
},
|
|
"\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
);
|
|
await loadRequestWorkspace(track, false);
|
|
setPageStatus("\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E.", "ok");
|
|
setClientHelpModal((prev) => ({
|
|
...prev,
|
|
loadingType: "",
|
|
status: { message: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E.", kind: "ok" },
|
|
lawyerChangeReason: requestType === "LAWYER_CHANGE_REQUEST" ? "" : prev.lawyerChangeReason
|
|
}));
|
|
} catch (error) {
|
|
setClientHelpModal((prev) => ({
|
|
...prev,
|
|
loadingType: "",
|
|
status: { message: (error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435", kind: "error" }
|
|
}));
|
|
}
|
|
},
|
|
[activeTrack, apiJson, loadRequestWorkspace, setPageStatus]
|
|
);
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const preferredTrack = String(params.get("track") || "").trim().toUpperCase();
|
|
void loadMyRequests(preferredTrack).catch((error) => {
|
|
setPageStatus((error == null ? void 0 : error.message) || "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u043A\u043B\u0438\u0435\u043D\u0442\u0430", "error");
|
|
});
|
|
}, [loadMyRequests, setPageStatus]);
|
|
const summary = requestModal.requestData || null;
|
|
const summaryStatusName = String((summary == null ? void 0 : summary.status_name) || statusLabel(summary == null ? void 0 : summary.status_code) || "-");
|
|
const viewerFullName = useMemo(() => {
|
|
var _a2;
|
|
const fullName = String(((_a2 = requestModal.requestData) == null ? void 0 : _a2.client_name) || "").trim();
|
|
return fullName || "\u041A\u043B\u0438\u0435\u043D\u0442";
|
|
}, [(_b = requestModal.requestData) == null ? void 0 : _b.client_name]);
|
|
const hasAnyUnreadUpdates = useMemo(
|
|
() => requestsList.some((row) => Number((row == null ? void 0 : row.viewer_unread_total) || 0) > 0 || Boolean(row == null ? void 0 : row.client_has_unread_updates)),
|
|
[requestsList]
|
|
);
|
|
return /* @__PURE__ */ React.createElement("div", { className: "client-page-shell" }, /* @__PURE__ */ React.createElement("main", { className: "main client-main" }, /* @__PURE__ */ React.createElement("div", { className: "topbar client-topbar" }, /* @__PURE__ */ React.createElement("div", { className: "client-topbar-copy" }, /* @__PURE__ */ React.createElement("div", { className: "client-title-row" }, /* @__PURE__ */ React.createElement("img", { className: "brand-mark", src: "/brand-mark.svg", alt: "", width: "24", height: "24" }), /* @__PURE__ */ React.createElement("h1", null, /* @__PURE__ */ React.createElement("span", null, "\u041B\u0438\u0447\u043D\u044B\u0439 \u043A\u0430\u0431\u0438\u043D\u0435\u0442"), /* @__PURE__ */ React.createElement("span", { className: "client-title-separator", "aria-hidden": "true" }, "\u2022"), /* @__PURE__ */ React.createElement("span", { className: "client-title-user" }, viewerFullName))), /* @__PURE__ */ React.createElement("div", { className: "client-help-inline" }, /* @__PURE__ */ React.createElement("p", { className: "muted" }, "\u041C\u044B \u0440\u0430\u0434\u044B \u0432\u0430\u043C \u043F\u043E\u043C\u043E\u0447\u044C"), /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "icon-btn workspace-head-icon",
|
|
id: "cabinet-help-open",
|
|
type: "button",
|
|
"data-tooltip": "\u041F\u043E\u043C\u043E\u0449\u044C \u0438 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u044F",
|
|
"aria-label": "\u041F\u043E\u043C\u043E\u0449\u044C \u0438 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u044F",
|
|
disabled: !canInteract,
|
|
onClick: openClientHelpModal
|
|
},
|
|
"?"
|
|
)))), /* @__PURE__ */ React.createElement("section", { className: "section active client-section" }, /* @__PURE__ */ React.createElement("div", { className: "section-head" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", null, "\u041C\u043E\u0438 \u0437\u0430\u044F\u0432\u043A\u0438"))), /* @__PURE__ */ React.createElement("div", { className: "client-request-toolbar" }, /* @__PURE__ */ React.createElement(
|
|
"button",
|
|
{
|
|
className: "btn secondary client-request-picker-trigger",
|
|
id: "client-request-open",
|
|
type: "button",
|
|
onClick: openRequestPicker,
|
|
disabled: requestModal.loading || !requestsList.length
|
|
},
|
|
/* @__PURE__ */ React.createElement("span", { className: "client-request-picker-trigger-label" }, "\u0417\u0430\u044F\u0432\u043A\u0430"),
|
|
/* @__PURE__ */ React.createElement("span", { className: "client-request-picker-trigger-track" }, activeTrack || "\u0412\u044B\u0431\u0440\u0430\u0442\u044C"),
|
|
hasAnyUnreadUpdates ? /* @__PURE__ */ React.createElement("span", { className: "client-request-news-dot active", "aria-hidden": "true" }) : null
|
|
)), /* @__PURE__ */ React.createElement("div", { className: "client-summary", id: "cabinet-summary", hidden: !summary }, /* @__PURE__ */ React.createElement("div", { className: "client-summary-row" }, /* @__PURE__ */ React.createElement("div", { className: "client-summary-chips" }, /* @__PURE__ */ React.createElement("span", { className: "client-summary-chip client-summary-chip-status" }, "\u0421\u0442\u0430\u0442\u0443\u0441: ", /* @__PURE__ */ React.createElement("span", { id: "cabinet-request-status" }, summary ? summaryStatusName : "-")), /* @__PURE__ */ React.createElement("span", { className: "client-summary-chip client-summary-chip-topic" }, "\u0422\u0435\u043C\u0430: ", /* @__PURE__ */ React.createElement("span", { id: "cabinet-request-topic" }, summary ? String(summary.topic_name || summary.topic_code || "-") : "-")), /* @__PURE__ */ React.createElement("span", { className: "client-summary-chip client-summary-chip-lawyer" }, "\u042E\u0440\u0438\u0441\u0442: ", /* @__PURE__ */ React.createElement("span", null, summary ? String(summary.assigned_lawyer_name || summary.assigned_lawyer_id || "\u041D\u0435 \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D") : "-"))), /* @__PURE__ */ React.createElement("div", { className: "client-summary-dates" }, /* @__PURE__ */ React.createElement("span", null, "\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430: ", /* @__PURE__ */ React.createElement("b", { id: "cabinet-request-updated" }, summary ? fmtShortDateTime(summary.updated_at) : "-"))))), /* @__PURE__ */ React.createElement(
|
|
RequestWorkspace,
|
|
{
|
|
viewerRole: "CLIENT",
|
|
viewerUserId: "",
|
|
loading: requestModal.loading,
|
|
trackNumber: requestModal.trackNumber,
|
|
requestData: requestModal.requestData,
|
|
financeSummary: requestModal.financeSummary,
|
|
invoices: requestModal.invoices || [],
|
|
statusRouteNodes: requestModal.statusRouteNodes || [],
|
|
statusHistory: requestModal.statusHistory || [],
|
|
availableStatuses: [],
|
|
currentImportantDateAt: requestModal.currentImportantDateAt || "",
|
|
pendingStatusChangePreset: null,
|
|
messages: requestModal.messages || [],
|
|
attachments: requestModal.attachments || [],
|
|
messageDraft: requestModal.messageDraft || "",
|
|
selectedFiles: requestModal.selectedFiles || [],
|
|
fileUploading: Boolean(requestModal.fileUploading),
|
|
status,
|
|
onMessageChange: updateMessageDraft,
|
|
onSendMessage: submitMessage,
|
|
onFilesSelect: appendFiles,
|
|
onRemoveSelectedFile: removeFile,
|
|
onClearSelectedFiles: clearFiles,
|
|
onLoadRequestDataBatch: loadRequestDataBatch,
|
|
onSaveRequestDataValues: saveRequestDataValues,
|
|
onUploadRequestAttachment: uploadPublicRequestAttachment,
|
|
onChangeStatus: () => Promise.resolve(null),
|
|
onDownloadInvoicePdf: downloadPublicInvoicePdf,
|
|
onLiveProbe: probeLiveState,
|
|
onTypingSignal: setTypingSignal,
|
|
AttachmentPreviewModalComponent: AttachmentPreviewModal,
|
|
StatusLineComponent: StatusLine,
|
|
domIds: {
|
|
messagesList: "cabinet-messages",
|
|
filesList: "cabinet-files",
|
|
messageBody: "cabinet-chat-body",
|
|
sendButton: "cabinet-chat-send",
|
|
fileInput: "cabinet-file-input",
|
|
dataRequestOverlay: "data-request-overlay",
|
|
dataRequestItems: "data-request-items",
|
|
dataRequestStatus: "data-request-status",
|
|
dataRequestSave: "data-request-save"
|
|
}
|
|
}
|
|
)), /* @__PURE__ */ React.createElement("p", { className: "status", id: "client-page-status" }, status.message)), /* @__PURE__ */ React.createElement(
|
|
RequestPickerModal,
|
|
{
|
|
open: requestPickerModal.open,
|
|
loading: requestPickerModal.loading,
|
|
requests: requestsList,
|
|
activeTrack,
|
|
onClose: closeRequestPicker,
|
|
onReload: reloadRequestPicker,
|
|
onSelect: selectRequestFromPicker
|
|
}
|
|
), /* @__PURE__ */ React.createElement(
|
|
ClientHelpModal,
|
|
{
|
|
open: clientHelpModal.open,
|
|
status: clientHelpModal.status,
|
|
loadingType: clientHelpModal.loadingType,
|
|
lawyerChangeReason: clientHelpModal.lawyerChangeReason,
|
|
curatorBlocked,
|
|
lawyerChangeBlocked,
|
|
onClose: closeClientHelpModal,
|
|
onReasonChange: (event) => setClientHelpModal((prev) => ({
|
|
...prev,
|
|
lawyerChangeReason: event.target.value,
|
|
status: { message: "", kind: "" }
|
|
})),
|
|
onSubmitCurator: () => submitServiceRequest({ type: "CURATOR_CONTACT", body: "\u041F\u0440\u043E\u0448\u0443 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u043A\u0443\u0440\u0430\u0442\u043E\u0440\u0430 \u043A \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0437\u0430\u044F\u0432\u043A\u0435." }),
|
|
onSubmitLawyerChange: () => submitServiceRequest({ type: "LAWYER_CHANGE_REQUEST", body: clientHelpModal.lawyerChangeReason })
|
|
}
|
|
), /* @__PURE__ */ React.createElement(GlobalTooltipLayer, null));
|
|
}
|
|
const root = document.getElementById("client-root");
|
|
if (root) {
|
|
ReactDOM.createRoot(root).render(/* @__PURE__ */ React.createElement(App, null));
|
|
}
|
|
})();
|
|
})();
|