From 653e75ad8a2e86d5b27fe3669a7b3056f9243a73 Mon Sep 17 00:00:00 2001 From: lizerk Date: Thu, 28 May 2026 15:42:57 +0300 Subject: [PATCH] procurement: polish request action controls --- frontend/src/app/globals.css | 22 ++++ frontend/src/app/procurement/page.tsx | 118 ++++++++++-------- .../ProcurementRequestDetailContent.tsx | 3 +- 3 files changed, 90 insertions(+), 53 deletions(-) diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index cbbcace6..16b1e01c 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -416,6 +416,28 @@ html[data-bs-theme="dark"] .app-input::-webkit-calendar-picker-indicator { background: var(--accent-primary-strong); } +.app-action-success { + border: 1px solid color-mix(in srgb, #10b981 20%, var(--border-strong)); + background: var(--success-soft); + color: #059669; +} + +.app-action-success:hover { + background: color-mix(in srgb, #10b981 18%, var(--surface-elevated)); + border-color: color-mix(in srgb, #10b981 30%, var(--border-strong)); +} + +.app-action-warning { + border: 1px solid color-mix(in srgb, #f59e0b 20%, var(--border-strong)); + background: var(--warning-soft); + color: #d97706; +} + +.app-action-warning:hover { + background: color-mix(in srgb, #f59e0b 18%, var(--surface-elevated)); + border-color: color-mix(in srgb, #f59e0b 30%, var(--border-strong)); +} + .app-action-secondary { border: 1px solid var(--border-strong); background: var(--surface-elevated); diff --git a/frontend/src/app/procurement/page.tsx b/frontend/src/app/procurement/page.tsx index 0aedeb20..8d9e7702 100644 --- a/frontend/src/app/procurement/page.tsx +++ b/frontend/src/app/procurement/page.tsx @@ -49,7 +49,7 @@ const statusMeta: Record = { pending: { label: "На согласовании", cls: "app-feedback-warning" }, approved: { label: "Одобрено", cls: "app-feedback-success" }, in_progress: { label: "В работе", cls: "app-selected" }, - completed: { label: "Завершено", cls: "app-selected" }, + completed: { label: "Завершено", cls: "app-feedback-success" }, rejected: { label: "Отклонено", cls: "app-feedback-danger" }, cancelled: { label: "Отменено", cls: "app-badge" }, }; @@ -61,8 +61,8 @@ const getStatusMeta = (status?: string | null) => ( const fulfillmentStatusClasses: Record = { pending: "app-badge", - partially_ordered: "app-feedback-warning", - ordered: "app-feedback-success", + partially_ordered: "app-selected", + ordered: "app-selected", partially_received: "app-feedback-warning", completed: "app-feedback-success", issues: "app-feedback-warning", @@ -71,6 +71,14 @@ const getFulfillmentStatusClass = (status?: string | null) => ( fulfillmentStatusClasses[String(status || "").toLowerCase() as ProcurementFulfillmentStatus] ?? "app-badge" ); +const getOrderedProgressClass = (ordered: number, total: number) => ( + total > 0 && ordered >= total ? "app-feedback-success" : total > 0 && ordered > 0 ? "app-selected" : "app-badge" +); +const getReceivedProgressClass = (received: number, total: number) => { + if (total <= 0 || received <= 0) return "app-badge"; + return received >= total ? "app-feedback-success" : "app-feedback-warning"; +}; + const urgencyMeta: Record = { low: { label: "Низкая", cls: "text-gray-500" }, medium: { label: "Средняя", cls: "text-sky-600" }, @@ -178,6 +186,7 @@ function ProcurementRequestActionButtons({ showLabels ? "gap-1.5 px-3 text-xs font-medium" : "w-9" }`; const label = (text: string) => (showLabels ? {text} : null); + const approvalIconSize = showLabels ? 14 : 18; return (
@@ -187,7 +196,7 @@ function ProcurementRequestActionButtons({ onClick={() => onSubmit(request.id)} disabled={busyKey === `submit-${request.id}`} title="Отправить на согласование" - className={buttonClass("app-action-primary")} + className={buttonClass("app-action-warning")} > {label("Отправить на согласование")} @@ -225,7 +234,7 @@ function ProcurementRequestActionButtons({ title="Одобрить" className={buttonClass("app-action-approve")} > - + {showLabels ? Одобрить : null} @@ -258,7 +267,7 @@ function ProcurementRequestActionButtons({ onClick={() => onComplete(request.id)} disabled={busyKey === `complete-${request.id}`} title="Закрыть заявку" - className={buttonClass("app-action-primary")} + className={buttonClass("app-action-success")} > {label("Закрыть заявку")} @@ -782,6 +791,12 @@ export default function ProcurementPage() { const canEditThis = Boolean(isEditableStatus && isAuthor); const canDeleteThis = Boolean(isEditableStatus && (isAuthor || canManage)); const canCancelThis = Boolean(isAuthor && !isFinal(st) && st !== "draft"); + const hasInlineRequestActions = Boolean( + (resolvedDetail.can_current_user_submit_for_approval ?? req.can_current_user_submit_for_approval) || + canApproveThis || + (resolvedDetail.can_current_user_start_work ?? req.can_current_user_start_work) || + (st === "in_progress" && isExecutor) + ); const hasSecondaryActions = canEditThis || canDeleteThis || canCancelThis; const expanded = expandedIds.has(req.id); const comments = commentsMap[req.id] || []; @@ -961,7 +976,7 @@ export default function ProcurementPage() { )} {itemsTotalCount > 0 && ( - + Получено {itemsReceivedCount}/{itemsTotalCount} поз. )} @@ -971,17 +986,17 @@ export default function ProcurementPage() { )} {totalRequestedQuantity > 0 && ( - + Заказано {totalOrderedQuantity}/{totalRequestedQuantity} шт. )} {totalRequestedQuantity > 0 && ( - + Получено {totalReceivedQuantity}/{totalRequestedQuantity} шт. )} {itemsPendingCount > 0 && ( - + Не обработано {itemsPendingCount} )} @@ -994,28 +1009,25 @@ export default function ProcurementPage() { )}
- {canApproveThis ? ( -
- - - - + {hasInlineRequestActions ? ( +
+ openRequestActionDialog("approve", id)} + onReject={(id) => openRequestActionDialog("reject", id)} + onStart={handleStart} + onComplete={handleComplete} + onCancel={handleCancel} + />
) : null} @@ -1132,6 +1144,27 @@ export default function ProcurementPage() { title={selectedRequest?.title || "Карточка заявки"} size="lg" closeOnClickOutside + footer={selectedRequest ? ( +
+ openRequestActionDialog("delete", id)} + onApprove={(id) => openRequestActionDialog("approve", id)} + onReject={(id) => openRequestActionDialog("reject", id)} + onStart={handleStart} + onComplete={handleComplete} + onCancel={(id) => openRequestActionDialog("cancel", id)} + /> +
+ ) : null} > {detailModalLoading ? (
@@ -1214,25 +1247,6 @@ export default function ProcurementPage() { onItemCommentDraftChange={(itemId, value) => setItemCommentDrafts((previous) => ({ ...previous, [itemId]: value }))} onAddItemComment={handleAddItemComment} onDeleteItemComment={handleDeleteItemComment} - footer={( - openRequestActionDialog("delete", id)} - onApprove={(id) => openRequestActionDialog("approve", id)} - onReject={(id) => openRequestActionDialog("reject", id)} - onStart={handleStart} - onComplete={handleComplete} - onCancel={(id) => openRequestActionDialog("cancel", id)} - /> - )} />
diff --git a/frontend/src/components/procurement/ProcurementRequestDetailContent.tsx b/frontend/src/components/procurement/ProcurementRequestDetailContent.tsx index 046ed195..f8e7b2bf 100644 --- a/frontend/src/components/procurement/ProcurementRequestDetailContent.tsx +++ b/frontend/src/components/procurement/ProcurementRequestDetailContent.tsx @@ -63,7 +63,8 @@ const problemExecutionStatuses = new Set([ ]); const executionStatusBadgeClass = (status?: ProcurementItemExecutionStatus) => { - if (status === "ordered" || status === "received") return "app-feedback-success"; + if (status === "ordered") return "app-selected"; + if (status === "received") return "app-feedback-success"; if (status === "rejected") return "app-feedback-danger"; if (status && problemExecutionStatuses.has(status)) return "app-feedback-warning"; return "app-badge";