From 3b660bf27eecb4bdb041fd62d622912e02c58711 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Mon, 19 Oct 2026 16:56:37 +0200 Subject: [PATCH] feat: enhance chapter and detail row interactions with improved animations and visibility controls - Adjusted drag handle alignment in QuoteChaptersDataTable for consistent width and positioning. - Implemented height and opacity transitions in useChapterCollapse for smoother chapter collapsing. - Updated QuoteItemsSortableTableRow to identify active rows with data attributes and apply consistent styling. - Refined detail row selection to limit checkboxes to detail rows only, improving user experience. - Enhanced visibility of controls on hover and focus for both chapter titles and detail rows. - Improved spacing and alignment for chapter titles and associated controls, ensuring a cohesive layout. - Added tests to validate new behaviors and interactions, ensuring functionality remains intact. --- .../components/QuoteChaptersDataTable.tsx | 339 +++++++++--------- .../QuoteItemsSortableDragHandleCell.tsx | 2 + .../components/QuoteItemsSortableTableRow.tsx | 3 +- .../editors/QuoteChapterEditor.test.tsx | 37 +- .../editors/QuoteDetailsCardEditor.tsx | 127 ++++--- .../app/quotes/hooks/useChapterCollapse.ts | 23 +- .../src/app/quotes/hooks/useDetailColumns.tsx | 2 +- doc/capitulos-cotizaciones.md | 44 +++ 8 files changed, 332 insertions(+), 245 deletions(-) diff --git a/client/src/app/quotes/components/QuoteChaptersDataTable.tsx b/client/src/app/quotes/components/QuoteChaptersDataTable.tsx index 4c8f298..42102fa 100644 --- a/client/src/app/quotes/components/QuoteChaptersDataTable.tsx +++ b/client/src/app/quotes/components/QuoteChaptersDataTable.tsx @@ -11,13 +11,13 @@ import { DialogFooter, DialogHeader, DialogTitle, - Label, Empty, + EmptyContent, + EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, - EmptyDescription, - EmptyContent, + Label, Select, SelectContent, SelectItem, @@ -29,12 +29,12 @@ import { import { Table, TableBody, TableCell, TableHead, TableRow } from "@/ui/table"; import { DndContext, - closestCenter, - pointerWithin, DragEndEvent, KeyboardCoordinateGetter, KeyboardSensor, PointerSensor, + closestCenter, + pointerWithin, useDroppable, useSensor, useSensors, @@ -48,37 +48,31 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table"; -import { - CopyIcon, - FolderIcon, - FolderOpenIcon, - GripVerticalIcon, - Trash2Icon, -} from "lucide-react"; +import { CopyIcon, FolderIcon, FolderOpenIcon, GripVerticalIcon, Trash2Icon } from "lucide-react"; import { Fragment, ReactNode, useMemo, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { - chapterSubtotal, - duplicateChapterSelection, EditorChapter, EditorItem, + chapterSubtotal, + duplicateChapterSelection, insertChapterItem, moveChapterItems, moveChapters, removeChapterSelection, ungroupChapters, } from "../chapterEditor"; -import { ChapterEditorController } from "../hooks/useChapterEditor"; import { useChapterCollapse } from "../hooks/useChapterCollapse"; +import { ChapterEditorController } from "../hooks/useChapterEditor"; import { QuoteItemsInlineRowActions } from "./QuoteItemsInlineRowActions"; +import { RowIdData } from "./QuoteItemsSortableDataTable"; import { QuoteItemsSortableDataTableToolbar } from "./QuoteItemsSortableDataTableToolbar"; import { QuoteItemsSortableTableRow } from "./QuoteItemsSortableTableRow"; -import { RowIdData } from "./QuoteItemsSortableDataTable"; -import { AppendEmptyRowButton } from "./AppendEmptyRowButton"; -import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton"; import { AppendBlockRowButton } from "./AppendBlockRowButton"; +import { AppendCatalogArticleRowButton } from "./AppendCatalogArticleRowButton"; +import { AppendEmptyRowButton } from "./AppendEmptyRowButton"; type Selection = { chapters: string[]; indexes: number[] }; const isChapter = (id: string | number) => String(id).startsWith("chapter:"); @@ -110,7 +104,11 @@ const keyboardCoordinates: KeyboardCoordinateGetter = ( }; function ChapterContent({ children }: { children: ReactNode }) { - return
{children}
; + return ( +
+ {children} +
+ ); } // Reserve the expand column in detail rows so all controls stay aligned. @@ -118,7 +116,15 @@ function ChapterSpacerCell() { return ; } -function EmptyChapterTarget({ id, columns, hidden, collapsed, onAddRow, onAddArticle, onAddBlock }: { +function EmptyChapterTarget({ + id, + columns, + hidden, + collapsed, + onAddRow, + onAddArticle, + onAddBlock, +}: { id: string; columns: number; hidden: boolean; @@ -147,7 +153,7 @@ function EmptyChapterTarget({ id, columns, hidden, collapsed, onAddRow, onAddArt {t("quotes.chapters.empty_title")} {t("quotes.chapters.empty")} - + @@ -164,24 +170,18 @@ function ChapterGroup({ index, columns, children, - selected, - onSelect, onActivate, onDuplicate, onRemove, - mixed, subtotal, }: { chapter: EditorChapter; index: number; columns: number; children: (collapsed: boolean, hidden: boolean) => ReactNode; - selected: boolean; - onSelect: (selected: boolean) => void; onActivate: () => void; onDuplicate: () => void; onRemove: () => void; - mixed: boolean; subtotal: string; }) { const { t } = useTranslation(); @@ -195,14 +195,23 @@ function ChapterGroup({ transform, transition, isDragging, - } = useSortable({ id: `chapter:${chapter.id}`, disabled: mixed }); + } = useSortable({ id: `chapter:${chapter.id}` }); return ( { setNodeRef(node); ref.current = node; }} - className='bg-background [&>tr:not([data-chapter-gap])>td]:border-b [&>tr:not([data-chapter-gap])>td]:border-b-border' + className={`bg-background + [&>tr:not([data-chapter-gap]):not([data-active-item])>td]:border-b + [&>tr:not([data-chapter-gap]):not([data-active-item])>td]:border-b-border + [&>tr[data-active-item]>td]:border-destructive/30 + [&>tr[data-active-item]>td:first-child]:border-l-4 + [&>tr[data-active-item]>td:first-child]:border-l-destructive + [&>tr[data-active-item]>td:last-child]:border-r + [&>tr[data-active-item=detail]>td]:border-t + [&>tr[data-active-item=detail]>td]:border-b-0 + [&>tr[data-active-item=actions]>td]:border-b`} style={{ transform: CSS.Translate.toString(transform), transition, @@ -215,16 +224,16 @@ function ChapterGroup({ )} - -
+ +
- - - - - onSelect(Boolean(value))} - aria-label={t("quotes.chapters.select", { number: index + 1 })} - /> - - +
( -
+