diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/frontend/next-env.d.ts +++ b/frontend/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/frontend/src/app/requests/page.tsx b/frontend/src/app/requests/page.tsx index 512f5884..21896ef8 100644 --- a/frontend/src/app/requests/page.tsx +++ b/frontend/src/app/requests/page.tsx @@ -2,6 +2,7 @@ import { AppShell } from "../../components/AppShell"; import { apiClient } from "@/lib/api"; +import { Modal } from "@/components/ui/Modal"; import { useUser } from "@/contexts/UserContext"; import { canManageRequests, canProcessRequests } from "@/lib/permissions"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -128,6 +129,7 @@ export default function RequestsPage() { const [filtersOpen, setFiltersOpen] = useState(false); const [ordering, setOrdering] = useState("-created_at"); const [attachmentPreview, setAttachmentPreview] = useState<{ url: string; name: string } | null>(null); + const [detailsRequest, setDetailsRequest] = useState(null); const [swipeMode, setSwipeMode] = useState(false); const [loading, setLoading] = useState(true); @@ -574,6 +576,33 @@ export default function RequestsPage() { setActionError(null); }; + const renderUserBadge = (person: User, large = false) => { + const personLink = userProfileLink(person); + const personName = displayUserName(person); + const chipContent = ( + <> + {person.avatar ? ( + {personName} + ) : ( + + {(person.first_name?.[0] || person.last_name?.[0] || "?").toUpperCase()} + + )} + {personName} + + ); + + return personLink ? ( + + {chipContent} + + ) : ( + + {chipContent} + + ); + }; + return ( {loading ? ( @@ -855,10 +884,17 @@ export default function RequestsPage() { )} -

- {requestTypeLabel}:{" "} - {requestTitle} -

+
Период: {item.date_from ? formatDate(item.date_from) : "—"}{item.date_to ? ` — ${formatDate(item.date_to)}` : ""}
@@ -872,7 +908,7 @@ export default function RequestsPage() { {summaryText ? ( -

{summaryText}

+

{summaryText}

) : null}
@@ -1132,6 +1168,157 @@ export default function RequestsPage() { )} + setDetailsRequest(null)} + title="Полная информация по заявлению" + size="lg" + > + {detailsRequest ? ( + (() => { + const detailAuthor = detailsRequest.employee || detailsRequest.created_by; + const detailApprover = detailsRequest.approver || detailsRequest.assigned_to; + const detailStatusKey = String(detailsRequest.status || "").toLowerCase(); + const detailTypeKey = String(detailsRequest.type || detailsRequest.request_type || "").toLowerCase(); + const detailStatus = statusMeta[detailStatusKey] ?? defaultStatusMeta; + const detailTypeLabel = requestTypeLabels[detailTypeKey] || String(detailsRequest.type || detailsRequest.request_type || "Другое"); + const detailTitle = detailsRequest.display_title || detailsRequest.title || "Без заголовка"; + const detailSummary = detailsRequest.comment || detailsRequest.description; + const detailDepartments = (detailsRequest.departments || []) + .map((id) => departmentNameMap.get(Number(id)) || `Отдел #${id}`); + const detailRecipients = detailsRequest.recipients || []; + const detailCcUsers = detailsRequest.cc_users || []; + const detailAttachmentUrl = detailsRequest.attachment_url || detailsRequest.attachment || ""; + const detailAttachmentName = detailAttachmentUrl ? decodeURIComponent(detailAttachmentUrl.split("/").pop() || "Вложение") : ""; + + return ( +
+
+
+
+ + {detailTypeLabel} + + + {detailStatus.label} + +
+
+

{detailTitle}

+ {detailSummary ? ( +
{detailSummary}
+ ) : ( +

Описание отсутствует

+ )} +
+
+
+ +
+
+

Даты

+
+
+

Период

+

{detailsRequest.date_from ? formatDate(detailsRequest.date_from) : "—"}{detailsRequest.date_to ? ` — ${formatDate(detailsRequest.date_to)}` : ""}

+
+
+

Создано

+

{formatDate(detailsRequest.created_at) || "—"}

+
+
+

Обновлено

+

{formatDate(detailsRequest.updated_at) || "—"}

+
+
+
+ +
+

Участники

+
+
+

Автор

+
+ {detailAuthor ? renderUserBadge(detailAuthor, true) : —} +
+
+
+

Решающий

+
+ {detailApprover ? renderUserBadge(detailApprover, true) : —} +
+
+
+
+
+ +
+

Отделы

+ {detailDepartments.length > 0 ? ( +
+ {detailDepartments.map((department) => ( + + {department} + + ))} +
+ ) : ( +

Отделы не указаны

+ )} +
+ +
+

Получатели

+ {detailRecipients.length > 0 ? ( +
+ {detailRecipients.map((recipient) => ( +
{renderUserBadge(recipient, true)}
+ ))} +
+ ) : ( +

Получатели не указаны

+ )} +
+ +
+

В копии

+ {detailCcUsers.length > 0 ? ( +
+ {detailCcUsers.map((ccUser) => ( +
{renderUserBadge(ccUser, true)}
+ ))} +
+ ) : ( +

Копия не указана

+ )} +
+ +
+

Вложение

+ {detailAttachmentUrl ? ( +
+ + + Открыть в новой вкладке + +
+ ) : ( +

Вложение отсутствует

+ )} +
+
+ ); + })() + ) : null} +
+ {/* ===== Modal create/edit ===== */} {isModalOpen && (
{ if (e.target === e.currentTarget) closeModal(); }}> diff --git a/frontend/src/components/requests/SwipeApprovalMode.tsx b/frontend/src/components/requests/SwipeApprovalMode.tsx index b936262d..6167c69b 100644 --- a/frontend/src/components/requests/SwipeApprovalMode.tsx +++ b/frontend/src/components/requests/SwipeApprovalMode.tsx @@ -232,7 +232,7 @@ export default function SwipeApprovalMode({ requests, onApprove, onReject, onClo
{/* title */} -

+

{typeLabel}: {req.display_title || req.title || "Без заголовка"}

@@ -244,7 +244,7 @@ export default function SwipeApprovalMode({ requests, onApprove, onReject, onClo {/* description */} {summaryText && ( -
+
{summaryText}
)}