feat(web): customs section with mark and unmark actions
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import type { ReactNode } from "react"
|
||||
import { ArrowRight, CircleCheck, Lock, Pencil, Trash2 } from "lucide-react"
|
||||
import { ArrowRight, BookmarkCheck, CircleCheck, Lock, Pencil, Trash2, Undo2 } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { cn } from "@/lib/utils"
|
||||
import type { ChangesetResponse, RecordView } from "@/api/types"
|
||||
|
||||
type Tone = "update" | "delete" | "readonly"
|
||||
type Tone = "update" | "delete" | "custom" | "readonly"
|
||||
|
||||
const TONE_META: Record<
|
||||
Tone,
|
||||
@@ -25,6 +26,13 @@ const TONE_META: Record<
|
||||
dot: "var(--diff-delete)",
|
||||
ring: "ring-[color-mix(in_oklch,var(--diff-delete),transparent_78%)]",
|
||||
},
|
||||
custom: {
|
||||
label: "Customs",
|
||||
empty: "Нет записей, помеченных как осознанные.",
|
||||
icon: BookmarkCheck,
|
||||
dot: "var(--diff-custom)",
|
||||
ring: "ring-[color-mix(in_oklch,var(--diff-custom),transparent_80%)]",
|
||||
},
|
||||
readonly: {
|
||||
label: "Read-only",
|
||||
empty: "Нет read-only записей.",
|
||||
@@ -46,11 +54,13 @@ function RecordRow({
|
||||
tone,
|
||||
checked,
|
||||
onToggle,
|
||||
action,
|
||||
}: {
|
||||
record: RecordView
|
||||
tone: Tone
|
||||
checked?: boolean
|
||||
onToggle?: (key: string) => void
|
||||
action?: ReactNode
|
||||
}) {
|
||||
const meta = TONE_META[tone]
|
||||
const showArrow = tone !== "delete"
|
||||
@@ -99,6 +109,8 @@ function RecordRow({
|
||||
read-only
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{action}
|
||||
</div>
|
||||
|
||||
{/* Values line: plain block-level text (not flex) so a long
|
||||
@@ -137,18 +149,21 @@ function Section({
|
||||
selected,
|
||||
onToggle,
|
||||
onToggleAll,
|
||||
renderAction,
|
||||
}: {
|
||||
tone: Tone
|
||||
records: RecordView[]
|
||||
selected?: Set<string>
|
||||
onToggle?: (key: string) => void
|
||||
onToggleAll?: (checked: boolean) => void
|
||||
renderAction?: (record: RecordView) => ReactNode
|
||||
}) {
|
||||
const meta = TONE_META[tone]
|
||||
const Icon = meta.icon
|
||||
// Read-only (NS/SOA) records are never selectable — only update/delete
|
||||
// sections receive selection props from DiffView.
|
||||
const selectable = tone !== "readonly" && !!selected && !!onToggle && !!onToggleAll
|
||||
// Read-only (NS/SOA) and custom records are never selectable — only
|
||||
// update/delete sections receive selection props from DiffView.
|
||||
const selectable =
|
||||
tone !== "readonly" && tone !== "custom" && !!selected && !!onToggle && !!onToggleAll
|
||||
const allSelected = selectable && records.length > 0 && records.every((r) => selected!.has(r.key))
|
||||
const someSelected = selectable && records.some((r) => selected!.has(r.key))
|
||||
const indeterminate = someSelected && !allSelected
|
||||
@@ -191,6 +206,7 @@ function Section({
|
||||
tone={tone}
|
||||
checked={selectable ? selected!.has(record.key) : undefined}
|
||||
onToggle={selectable ? onToggle : undefined}
|
||||
action={renderAction?.(record)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -207,6 +223,8 @@ export function DiffView({
|
||||
onTogglePrune,
|
||||
onToggleAllUpdates,
|
||||
onToggleAllPrunes,
|
||||
onMarkCustom,
|
||||
onUnmarkCustom,
|
||||
footerExtra,
|
||||
}: {
|
||||
changeset: ChangesetResponse
|
||||
@@ -216,6 +234,8 @@ export function DiffView({
|
||||
onTogglePrune: (key: string) => void
|
||||
onToggleAllUpdates: (checked: boolean) => void
|
||||
onToggleAllPrunes: (checked: boolean) => void
|
||||
onMarkCustom?: (key: string) => void
|
||||
onUnmarkCustom?: (key: string) => void
|
||||
footerExtra?: ReactNode
|
||||
}) {
|
||||
// Defensive: a field may arrive as null (e.g. a nil slice from an older
|
||||
@@ -235,6 +255,36 @@ export function DiffView({
|
||||
selected={selectedPrunes}
|
||||
onToggle={onTogglePrune}
|
||||
onToggleAll={onToggleAllPrunes}
|
||||
renderAction={
|
||||
onMarkCustom &&
|
||||
((record) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`В customs ${record.type} ${record.name}`}
|
||||
onClick={() => onMarkCustom(record.key)}
|
||||
>
|
||||
<BookmarkCheck className="size-3.5" strokeWidth={1.75} />
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
/>
|
||||
<Section
|
||||
tone="custom"
|
||||
records={changeset.customs ?? []}
|
||||
renderAction={
|
||||
onUnmarkCustom &&
|
||||
((record) => (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={`Вернуть в дифф ${record.type} ${record.name}`}
|
||||
onClick={() => onUnmarkCustom(record.key)}
|
||||
>
|
||||
<Undo2 className="size-3.5" strokeWidth={1.75} />
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
/>
|
||||
<Section tone="readonly" records={changeset.readOnly ?? []} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user