diff --git a/frontend/src/components/requests/RequestListControls.tsx b/frontend/src/components/requests/RequestListControls.tsx index f7e1a3fa..0c528758 100644 --- a/frontend/src/components/requests/RequestListControls.tsx +++ b/frontend/src/components/requests/RequestListControls.tsx @@ -1,7 +1,7 @@ import { orderingOptions, requestTypeLabels, statusMeta } from "@/hooks/useRequestsPage"; import { displayUserName } from "@/lib/shared"; import type { User } from "@/types/api"; -import { ArrowUpDown, ChevronDown, Filter, Plus, Search, X, Zap } from "lucide-react"; +import { ArrowUpDown, ChevronDown, Filter, Plus, Search, Zap } from "lucide-react"; type RequestListControlsState = { activeFiltersCount: number; @@ -55,209 +55,178 @@ export function RequestListControls({ state, }: RequestListControlsProps) { return ( -
-
-
+ <> +
+

Заявления

- {state.pendingDecisionCount > 0 ? ( - - {state.pendingDecisionCount} на рассмотрении - - ) : null} -
- -
{state.pendingDecisionCount > 0 ? ( ) : null} -
+
{feedback.actionError ? ( -

{feedback.actionError}

+

{feedback.actionError}

) : null} {feedback.actionSuccess ? ( -

{feedback.actionSuccess}

+

{feedback.actionSuccess}

) : null} -
-
-
- - actions.setSearch(event.target.value)} - placeholder="Поиск по заявлениям" - className="app-input w-full rounded-lg py-2.5 pl-9 pr-3 text-sm" - /> -
- -
- - -
- - - -
-
+
+
+ + actions.setSearch(event.target.value)} + placeholder="Поиск по заявлениям" + className="app-input w-full rounded-lg py-2.5 pl-9 pr-3 text-sm" + />
- {state.filtersOpen ? ( -
-
- + - +
+ + + +
+
- + {state.filtersOpen ? ( +
+ - + + + + + + +
+ +
+ actions.setCreatedFromFilter(event.target.value)} + className="app-input flex-1 rounded-lg px-3 py-2 text-sm" + /> + actions.setCreatedToFilter(event.target.value)} + className="app-input flex-1 rounded-lg px-3 py-2 text-sm" + />
- -
-
- -
- actions.setCreatedFromFilter(event.target.value)} - className="app-input rounded-lg px-3 py-2 text-sm" - /> - actions.setCreatedToFilter(event.target.value)} - className="app-input rounded-lg px-3 py-2 text-sm" - /> -
-
- -
- -
- actions.setPeriodFromFilter(event.target.value)} - className="app-input rounded-lg px-3 py-2 text-sm" - /> - actions.setPeriodToFilter(event.target.value)} - className="app-input rounded-lg px-3 py-2 text-sm" - /> -
-
-
- - {state.hasActiveFilters ? ( -
- -
- ) : null}
- ) : null} -
-
+ +
+ +
+ actions.setPeriodFromFilter(event.target.value)} + className="app-input flex-1 rounded-lg px-3 py-2 text-sm" + /> + actions.setPeriodToFilter(event.target.value)} + className="app-input flex-1 rounded-lg px-3 py-2 text-sm" + /> +
+
+ + {state.hasActiveFilters ? ( + + ) : null} +
+ ) : null} + ); } diff --git a/frontend/src/components/requests/RequestListItem.tsx b/frontend/src/components/requests/RequestListItem.tsx index eaacbbbe..4570fa7d 100644 --- a/frontend/src/components/requests/RequestListItem.tsx +++ b/frontend/src/components/requests/RequestListItem.tsx @@ -5,7 +5,7 @@ import { statusMeta, type RequestAttachmentPreview, } from "@/hooks/useRequestsPage"; -import { displayUserName, formatDate, formatDateTime, userProfileLink } from "@/lib/shared"; +import { displayUserName, formatDate, userProfileLink } from "@/lib/shared"; import type { Request, RequestComment, User } from "@/types/api"; import { Ban, @@ -18,7 +18,7 @@ import { ThumbsUp, Trash2, } from "lucide-react"; -import type { ReactNode, Ref } from "react"; +import type { Ref } from "react"; import { RequestAvatar } from "./RequestAvatar"; import { RequestUserBadge } from "./RequestUserBadge"; @@ -49,64 +49,8 @@ type RequestListItemProps = { rowOpen: boolean; }; -type RequestMetaFieldProps = { - label: string; - value: ReactNode; -}; - -type RequestAudienceRowProps = { - count?: number; - currentUserId?: number | null; - emptyText: string; - label: string; - people: User[]; -}; - -function RequestMetaField({ label, value }: RequestMetaFieldProps) { - return ( -
-

{label}

-
{value}
-
- ); -} - -function RequestAudienceRow({ - count, - currentUserId, - emptyText, - label, - people, -}: RequestAudienceRowProps) { - return ( -
-

{label}

- {people.length > 0 ? ( -
- {people.slice(0, 2).map((person) => ( - - ))} - {people.length > 2 ? ( - - +{people.length - 2} - - ) : null} -
- ) : ( -

{count ? count : emptyText}

- )} -
- ); -} - function renderDecisionMaker(decisionMaker: User | undefined, currentUserId?: number | null) { - if (!decisionMaker) { - return -; - } + if (!decisionMaker) return —; const decisionMakerLink = userProfileLink(decisionMaker, currentUserId); const decisionMakerName = displayUserName(decisionMaker); @@ -149,10 +93,10 @@ export function RequestListItem({ const requestAuthor = request.employee || request.created_by; const authorName = displayUserName(requestAuthor); const authorFallback = (requestAuthor?.first_name?.[0] || requestAuthor?.last_name?.[0] || "?").toUpperCase(); - const authorLink = requestAuthor ? userProfileLink(requestAuthor, currentUserId) : null; const statusKey = String(request.status || "").toLowerCase(); - const status = statusMeta[statusKey] ?? defaultStatusMeta; const typeKey = String(request.type || request.request_type || "").toLowerCase(); + const status = statusMeta[statusKey] ?? defaultStatusMeta; + const authorLink = requestAuthor ? userProfileLink(requestAuthor, currentUserId) : null; const typeLabel = requestTypeLabels[typeKey] || String(request.type || request.request_type || "Другое"); const title = request.display_title || request.title || "Без заголовка"; const canProcess = Boolean(statusKey === "pending" && request.can_decide); @@ -162,35 +106,30 @@ export function RequestListItem({ const canDelete = Boolean(isAuthor && !isFinal(statusKey)); const hasSecondaryActions = canCancel || canEdit || canDelete; const canComment = statusKey !== "draft"; - const summary = request.comment || request.description; - const commentCount = request.comments_count ?? comments.length; - const trimmedCommentDraft = commentDraft.trim(); - const recipients = request.recipients || []; - const ccUsers = request.cc_users || []; - const decisionMaker = request.approver || request.assigned_to; const departmentLabels = (request.departments || []) .map((id) => departmentNameMap.get(Number(id)) || `Отдел #${id}`) .join(", "); + const recipients = request.recipients || []; + const ccUsers = request.cc_users || []; + const summary = request.comment || request.description; const attachmentUrl = request.attachment_url || request.attachment || ""; const attachmentName = attachmentUrl ? decodeURIComponent(attachmentUrl.split("/").pop() || "Вложение") : ""; - const hasExpandedPanels = rowOpen || commentsOpen; + const commentCount = request.comments_count ?? comments.length; + const decisionMaker = request.approver || request.assigned_to; return (
-
+
@@ -199,7 +138,7 @@ export function RequestListItem({ title={canComment ? `Комментарии (${commentCount})` : "Комментарии для черновика недоступны"} onClick={() => canComment && void onToggleComments(request.id)} disabled={!canComment} - className={`relative inline-flex h-8 w-8 items-center justify-center rounded-lg transition disabled:cursor-not-allowed disabled:opacity-50 ${commentsOpen ? "app-selected app-accent-text" : "app-action-secondary"}`} + className="app-action-secondary relative inline-flex h-8 w-8 items-center justify-center rounded-lg disabled:cursor-not-allowed disabled:opacity-50" > {commentCount > 0 ? ( @@ -212,8 +151,8 @@ export function RequestListItem({
-
-
+
+
{authorLink ? ( {authorName}
)} - - {typeLabel} +
+ + + +
+ + Период: {request.date_from ? formatDate(request.date_from) : "—"} + {request.date_to ? ` — ${formatDate(request.date_to)}` : ""}
- -
- - -
- - Период: {request.date_from ? formatDate(request.date_from) : "-"} - {request.date_to ? ` - ${formatDate(request.date_to)}` : ""} - - Создано: {formatDate(request.created_at) || "-"} -
-
- - {summary ? ( -

- {summary} -

- ) : null}
-
-
+
+
{status.label} - - {canProcess ? ( - <> - - - - ) : null} - {hasSecondaryActions ? (
- {hasExpandedPanels ? ( -
-
- {rowOpen ? ( -
-
-

- Подробности -

- {attachmentUrl ? ( - - ) : null} -
+ {summary ? ( +

+ {summary} +

+ ) : null} -
-
- - - - -} - /> -
- -
- - -
-
-
- ) : null} - - {commentsOpen ? ( -
-
-

- Комментарии -

- - {commentCount} - -
- -
- {comments.length === 0 ? ( -

- Комментариев пока нет -

- ) : comments.map((comment) => ( -
-
- {displayUserName(comment.author)} -
- - {formatDateTime(comment.created_at) || "-"} - - {Boolean(comment.author?.id && currentUserId === comment.author.id) ? ( - - ) : null} -
-
-

- {comment.text} -

-
- ))} -
- -
- onSetCommentDraft(request.id, event.target.value)} - placeholder="Добавить комментарий" - className="app-input flex-1 rounded-lg px-3 py-2.5 text-sm" - /> - -
-
- ) : null} -
+ {canProcess ? ( +
+ + + +
) : null}
+ + {(rowOpen || commentsOpen) ? ( +
+ {rowOpen ? ( +
+
+
+ Принял решение:{" "} + {renderDecisionMaker(decisionMaker, currentUserId)} +
+
+ Создано:{" "} + + {formatDate(request.created_at) || "—"} + +
+
+ Обновлено:{" "} + + {formatDate(request.updated_at) || "—"} + +
+ {departmentLabels ? ( +
+ Отделы:{" "} + {departmentLabels} +
+ ) : null} +
+ +
+
+ Получатели: +
+ {recipients.slice(0, 2).map((recipient) => ( + + ))} + {recipients.length === 0 ? ( + {request.recipient_count ?? 0} + ) : null} + {recipients.length > 2 ? ( + + +{recipients.length - 2} + + ) : null} +
+
+ +
+ В копии: +
+ {ccUsers.slice(0, 2).map((ccUser) => ( + + ))} + {ccUsers.length === 0 ? — : null} + {ccUsers.length > 2 ? ( + + +{ccUsers.length - 2} + + ) : null} +
+
+ + {attachmentUrl ? ( +
+ +
+ ) : null} +
+
+ ) : null} + + {commentsOpen ? ( +
+
+ {comments.length === 0 ? ( +

Комментариев пока нет

+ ) : ( + comments.map((comment) => ( +
+
+ {displayUserName(comment.author)} +
+ {formatDate(comment.created_at)} + {Boolean(comment.author?.id && currentUserId === comment.author.id) ? ( + + ) : null} +
+
+

{comment.text}

+
+ )) + )} +
+
+ onSetCommentDraft(request.id, event.target.value)} + placeholder="Добавить комментарий" + className="app-input flex-1 rounded-lg px-3 py-2 text-xs" + /> + +
+
+ ) : null} +
+ ) : null}
); diff --git a/frontend/src/components/requests/RequestListSection.tsx b/frontend/src/components/requests/RequestListSection.tsx index 2cc1500d..dc7d946c 100644 --- a/frontend/src/components/requests/RequestListSection.tsx +++ b/frontend/src/components/requests/RequestListSection.tsx @@ -65,7 +65,7 @@ export function RequestListSection({ }: RequestListSectionProps) { return ( <> -
+
{requests.length === 0 ? (