diff --git a/docs/guides/frontend/04-PATTERNS.md b/docs/guides/frontend/04-PATTERNS.md index 6a19e0a9..b9230d0a 100644 --- a/docs/guides/frontend/04-PATTERNS.md +++ b/docs/guides/frontend/04-PATTERNS.md @@ -135,6 +135,7 @@ Target state: | Обычный input / textarea / select | `app-input` / `app-select` + `rounded-lg` | нет | `ok` | | Searchable dropdown trigger | shared `SearchableSelect*` + `rounded-lg` | compose inline-row может быть крупнее по shell | `ok` | | Compose address row | shared select pattern на базе `app-input` | `rounded-2xl` допустим для compose-shell строки | `ok` | +| Inline comment composer | shared `CommentControls` + icon-only send action | feed/footer variant может добавлять attachment actions перед input | `ok` | | Helper / alert / selected block | `app-selected`, `app-feedback-*`, `app-surface-muted` + `rounded-lg`/`rounded-xl` | зависит от веса блока | `needs refactor` | | Pills / status chips / avatar | `rounded-full` | нет | `ok` | | `rounded-md` у новых карточек и полей | не использовать | только legacy до отдельной переработки | `legacy` | diff --git a/docs/guides/frontend/06-COMPONENT-INVENTORY.md b/docs/guides/frontend/06-COMPONENT-INVENTORY.md index b82b7012..3d75dbe3 100644 --- a/docs/guides/frontend/06-COMPONENT-INVENTORY.md +++ b/docs/guides/frontend/06-COMPONENT-INVENTORY.md @@ -254,6 +254,7 @@ - modal: `Modal` - searchable dropdown: `SearchableSelect*` - compose address line: `SearchableSelectMulti` с compose-like layout +- comment composer / delete action: `shared/CommentControls.tsx` - primary/secondary surface: `app-surface` / `app-surface-muted` - feedback block: `app-selected`, `app-feedback-*` diff --git a/frontend/src/app/equipment/page.tsx b/frontend/src/app/equipment/page.tsx index e0568cae..ed9e2191 100644 --- a/frontend/src/app/equipment/page.tsx +++ b/frontend/src/app/equipment/page.tsx @@ -8,6 +8,7 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Archive, ArrowRightLeft, ArrowUpDown, Check, ChevronDown, ChevronRight, Copy, Download, ExternalLink, Filter, MessageSquare, Monitor, Pencil, Plus, QrCode, Search, Shield, Trash2, Wrench } from "lucide-react"; import { SearchableSelectSingle } from "@/components/shared/SearchableSelect"; +import { CommentComposer, CommentDeleteButton } from "@/components/shared/CommentControls"; import { formatDate, formatMoney } from "@/lib/shared"; import { useEquipmentPage } from "@/hooks/useEquipmentPage"; import { Modal } from "@/components/ui"; @@ -759,7 +760,11 @@ function EquipmentPageContent() { {displayUserName(c.author)}
{formatDate(c.created_at)} - {canDel && } + {canDel ? ( + handleDeleteComment(item.id, c.id)} + /> + ) : null}

{c.text}

@@ -768,9 +773,13 @@ function EquipmentPageContent() { }) )} -
- setCommentDrafts((p) => ({ ...p, [item.id]: e.target.value }))} placeholder="Добавить комментарий" className="app-input flex-1 rounded-lg px-3 py-2 text-xs" /> - +
+ setCommentDrafts((p) => ({ ...p, [item.id]: value }))} + onSubmit={() => handleAddComment(item.id)} + disabled={busyKey === `comment-${item.id}`} + />
)} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 4ebcf179..47ab5118 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -208,7 +208,7 @@ function HomePageContent() { async function loadPosts() { try { await refreshPosts(); - } catch (err: any) { + } catch (err: unknown) { console.error('Ошибка загрузки ленты:', err); setError('Не удалось загрузить ленту'); } finally { @@ -376,8 +376,8 @@ function HomePageContent() { try { const response = await apiClient.getPostLikers(postId); setLikesUsersMap((prev) => ({ ...prev, [postId]: response.results || [] })); - } catch (err: any) { - const message = String(err?.message || ""); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : ""; if ( message.includes("404") || message.includes("NetworkError") || @@ -579,8 +579,8 @@ function HomePageContent() { setCreatePostOpen(false); setEditingPostId(null); - } catch (err: any) { - const message = String(err?.message || "Не удалось сохранить публикацию"); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : "Не удалось сохранить публикацию"; setCreateError(message); } finally { setCreateSubmitting(false); @@ -980,7 +980,7 @@ function HomePageContent() { )} )} -
+
{ setCommentImage(e.target.files?.[0] || null); e.target.value = ""; }} /> { setCommentAttachment(e.target.files?.[0] || null); e.target.value = ""; }} /> diff --git a/frontend/src/app/procurement/page.tsx b/frontend/src/app/procurement/page.tsx index ba1c5f84..c2dfe7f9 100644 --- a/frontend/src/app/procurement/page.tsx +++ b/frontend/src/app/procurement/page.tsx @@ -31,6 +31,7 @@ import { X, XCircle, } from "lucide-react"; +import { CommentComposer, CommentDeleteButton } from "@/components/shared/CommentControls"; import { SearchableSelectSingle } from "@/components/shared/SearchableSelect"; import { formatDate, formatMoney } from "@/lib/shared"; import { useProcurementPage } from "@/hooks/useProcurementPage"; @@ -870,9 +871,9 @@ export default function ProcurementPage() {
{fmt(comment.created_at)} {canDeleteCommentThis ? ( - + handleDeleteComment(req.id, comment.id)} + /> ) : null}
@@ -882,9 +883,13 @@ export default function ProcurementPage() { }) )}
-
- setCommentDrafts((previous) => ({ ...previous, [req.id]: e.target.value }))} placeholder="Добавить комментарий" className="app-input flex-1 rounded-lg px-3 py-2 text-xs" /> - +
+ setCommentDrafts((previous) => ({ ...previous, [req.id]: value }))} + onSubmit={() => handleAddComment(req.id)} + disabled={busyKey === `comment-${req.id}`} + />
) : null} @@ -1006,9 +1011,9 @@ export default function ProcurementPage() {
{fmt(comment.created_at)} {canDeleteCommentThis ? ( - + handleDeleteComment(selectedRequest.id, comment.id)} + /> ) : null}
@@ -1018,9 +1023,13 @@ export default function ProcurementPage() { }) )} -
- setCommentDrafts((previous) => ({ ...previous, [selectedRequest.id]: e.target.value }))} placeholder="Добавить комментарий" className="app-input flex-1 rounded-lg px-3 py-2 text-xs" /> - +
+ setCommentDrafts((previous) => ({ ...previous, [selectedRequest.id]: value }))} + onSubmit={() => handleAddComment(selectedRequest.id)} + disabled={busyKey === `comment-${selectedRequest.id}`} + />
diff --git a/frontend/src/components/documents/DocumentComments.tsx b/frontend/src/components/documents/DocumentComments.tsx index a727dd8c..6ab0e282 100644 --- a/frontend/src/components/documents/DocumentComments.tsx +++ b/frontend/src/components/documents/DocumentComments.tsx @@ -1,10 +1,11 @@ "use client"; import { useState, useEffect } from "react"; -import { MessageCircle, Send, Edit2, Trash2, CornerDownRight } from "lucide-react"; +import { MessageCircle, Edit2, CornerDownRight } from "lucide-react"; import { apiClient } from "@/lib/api"; import { toast } from "sonner"; import { useUser } from "@/contexts/UserContext"; +import { CommentComposer, CommentDeleteButton } from "@/components/shared/CommentControls"; interface Comment { id: number; @@ -187,13 +188,7 @@ export function DocumentComments({ documentId }: DocumentCommentsProps) { > - + handleDeleteComment(comment.id)} /> )} @@ -251,34 +246,18 @@ export function DocumentComments({ documentId }: DocumentCommentsProps) { {/* Reply form */} {replyTo === comment.id && (
-
-