@open-mercato/ui 0.6.6-develop.6253.1.a8c2b49d90 → 0.6.6-develop.6256.1.9fc16aedc4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/DataTable.js +60 -11
- package/dist/backend/DataTable.js.map +3 -3
- package/dist/backend/PerspectiveSidebar.js +1 -1
- package/dist/backend/PerspectiveSidebar.js.map +2 -2
- package/package.json +3 -3
- package/src/backend/DataTable.tsx +79 -11
- package/src/backend/PerspectiveSidebar.tsx +2 -2
- package/src/backend/__tests__/DataTable.perspectiveGuardedMutation.test.tsx +16 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/PerspectiveSidebar.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ArrowLeft, Plus } from 'lucide-react'\nimport { Alert } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { Spinner } from '../primitives/spinner'\nimport { useConfirmDialog } from './confirm-dialog'\nimport { flash } from './FlashMessages'\nimport { surfaceRecordConflict } from './conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n PerspectiveDto,\n PerspectiveSettings,\n RolePerspectiveDto,\n} from '@open-mercato/shared/modules/perspectives/types'\nimport { ViewChip } from './views/ViewChip'\nimport { NewViewForm } from './views/NewViewForm'\nimport { ShareForm } from './views/ShareForm'\nimport { type SidebarMode } from './views/types'\nimport { ColumnChooserSection, type ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type { ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type PerspectiveSidebarProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n loading: boolean\n perspectives: PerspectiveDto[]\n rolePerspectives: RolePerspectiveDto[]\n roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>\n activePerspectiveId: string | null\n onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void\n onDeletePerspective: (perspectiveId: string) => Promise<void>\n onClearRole: (roleId: string) => Promise<void>\n onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>\n canApplyToRoles: boolean\n availableColumns: ColumnChooserField[]\n visibleColumnKeys: string[]\n columnOrder: string[]\n onToggleColumn: (key: string) => void\n onReorderColumns: (orderedIds: string[]) => void\n saving: boolean\n deletingIds: string[]\n roleClearingIds: string[]\n apiWarning?: string | null\n}\n\nconst emptyArray: any[] = []\n\nexport function PerspectiveSidebar({\n open,\n onOpenChange,\n loading,\n perspectives,\n rolePerspectives,\n roles,\n activePerspectiveId,\n onActivatePerspective,\n onDeletePerspective,\n onClearRole,\n onSave,\n canApplyToRoles,\n availableColumns,\n visibleColumnKeys,\n columnOrder,\n onToggleColumn,\n onReorderColumns,\n saving,\n deletingIds,\n roleClearingIds,\n apiWarning,\n}: PerspectiveSidebarProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!open) return\n if (typeof document === 'undefined') return\n document.body.dataset.columnChooserOpen = 'true'\n return () => {\n delete document.body.dataset.columnChooserOpen\n }\n }, [open])\n\n function perspectiveLabel(p: PerspectiveDto | RolePerspectiveDto) {\n return p.name.trim().length ? p.name : t('ui.perspectives.untitled', 'Untitled view')\n }\n\n const [name, setName] = React.useState('')\n const [isDefault, setIsDefault] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mode, setMode] = React.useState<SidebarMode>({ type: 'idle' })\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameValue, setRenameValue] = React.useState('')\n const [shareRoles, setShareRoles] = React.useState<string[]>([])\n const [shareSetDefault, setShareSetDefault] = React.useState(false)\n const [sharedIds, setSharedIds] = React.useState<Set<string>>(new Set())\n const [pendingCloneBaselineIds, setPendingCloneBaselineIds] = React.useState<Set<string> | null>(null)\n\n const autosaveRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const isDefaultUserChangeRef = React.useRef(false)\n const isDefaultRef = React.useRef(isDefault)\n isDefaultRef.current = isDefault\n\n const onSaveRef = React.useRef(onSave)\n onSaveRef.current = onSave\n\n const perspectivesRef = React.useRef(perspectives)\n perspectivesRef.current = perspectives\n\n const flushAutosave = React.useCallback(() => {\n if (autosaveRef.current) {\n clearTimeout(autosaveRef.current)\n autosaveRef.current = null\n }\n }, [])\n\n const scheduleAutosave = React.useCallback(() => {\n if (!activePerspectiveId || mode.type === 'new') return\n const activePersonal = perspectivesRef.current.find((p) => p.id === activePerspectiveId)\n if (!activePersonal) return\n flushAutosave()\n const targetId = activePersonal.id\n const targetName = activePersonal.name\n autosaveRef.current = setTimeout(async () => {\n autosaveRef.current = null\n try {\n await onSaveRef.current({\n name: targetName,\n isDefault: isDefaultRef.current,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: targetId,\n })\n flash(t('ui.perspectives.autosave.success', 'View saved'), 'success')\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n flash(t('ui.perspectives.autosave.error', 'Failed to save view'), 'error')\n }\n }, 400)\n }, [activePerspectiveId, mode.type, flushAutosave, t])\n\n React.useEffect(() => {\n return () => { flushAutosave() }\n }, [flushAutosave])\n\n const resetMode = () => {\n setMode({ type: 'idle' })\n setName('')\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startNewMode = () => {\n setMode({ type: 'new' })\n setName('')\n setIsDefault(false)\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startShareMode = (p: PerspectiveDto) => {\n setMode({ type: 'share', perspectiveId: p.id, perspectiveName: p.name, perspectiveIsDefault: p.isDefault })\n setName('')\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n React.useEffect(() => {\n if (!open) {\n flushAutosave()\n setError(null)\n resetMode()\n setRenamingId(null)\n setSharedIds(new Set())\n setPendingCloneBaselineIds(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const active = perspectives.find((p) => p.id === activePerspectiveId)\n ?? rolePerspectives.find((p) => p.id === activePerspectiveId)\n isDefaultUserChangeRef.current = false\n if (active) {\n setIsDefault(active.isDefault)\n } else {\n setIsDefault(false)\n }\n requestAnimationFrame(() => { isDefaultUserChangeRef.current = true })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, activePerspectiveId])\n\n const handleSaveNew = async () => {\n setError(null)\n try {\n await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null })\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to save view')\n }\n }\n\n const handleToggleColumnWithAutosave = React.useCallback((key: string) => {\n onToggleColumn(key)\n scheduleAutosave()\n }, [onToggleColumn, scheduleAutosave])\n\n const handleReorderColumnsWithAutosave = React.useCallback((orderedIds: string[]) => {\n onReorderColumns(orderedIds)\n scheduleAutosave()\n }, [onReorderColumns, scheduleAutosave])\n\n const handleRename = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const trimmed = renameValue.trim()\n if (!trimmed) {\n setRenamingId(null)\n return\n }\n const conflict = perspectivesRef.current.some(\n (item) => item.id !== p.id && item.name.trim() === trimmed,\n )\n if (conflict) {\n flash(t('ui.perspectives.error.nameExists', 'View with this name already exists'), 'error')\n return\n }\n setError(null)\n try {\n await onSave({\n name: trimmed,\n isDefault: p.isDefault,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: p.id,\n })\n setRenamingId(null)\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to rename view')\n }\n }\n\n const handleClone = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const originalName = perspectiveLabel(p)\n setError(null)\n const baseline = new Set(perspectivesRef.current.map((item) => item.id))\n const originalSettingsKey = JSON.stringify(p.settings ?? {})\n const sharedRoleIds = Array.from(\n new Set(\n rolePerspectives\n .filter((rp) => JSON.stringify(rp.settings ?? {}) === originalSettingsKey)\n .map((rp) => rp.roleId),\n ),\n )\n const buildCloneName = (n: number) =>\n n <= 1 ? `${originalName} (copy)` : `${originalName} (copy ${n})`\n const existingNames = new Set(\n perspectivesRef.current.map((item) => item.name.trim()),\n )\n let counter = 1\n while (existingNames.has(buildCloneName(counter))) {\n counter += 1\n }\n const clonedSettings = structuredClone(p.settings)\n const MAX_ATTEMPTS = 50\n let lastErr: unknown = null\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n try {\n await onSave({\n name: buildCloneName(counter),\n isDefault: false,\n applyToRoles: sharedRoleIds,\n setRoleDefault: false,\n perspectiveId: null,\n settings: clonedSettings,\n })\n setPendingCloneBaselineIds(baseline)\n return\n } catch (err: unknown) {\n const errMessage = err instanceof Error ? err.message : ''\n const msg = errMessage.toLowerCase()\n const isDuplicate = msg.includes('duplicate key') || msg.includes('unique constraint')\n if (!isDuplicate) {\n if (surfaceRecordConflict(err, t)) return\n setError(errMessage || 'Failed to clone view')\n return\n }\n lastErr = err\n counter += 1\n }\n }\n if (surfaceRecordConflict(lastErr, t)) return\n setError(lastErr instanceof Error ? lastErr.message : 'Failed to clone view')\n }\n\n React.useEffect(() => {\n if (!pendingCloneBaselineIds) return\n const created = perspectives.find((item) => !pendingCloneBaselineIds.has(item.id))\n if (!created) return\n setRenamingId(created.id)\n setRenameValue(created.name)\n setPendingCloneBaselineIds(null)\n }, [perspectives, pendingCloneBaselineIds])\n\n const handleDelete = async (p: PerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.delete.text', 'This view will be removed for you and all shared roles. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onDeletePerspective(p.id)\n }\n }\n\n const handleDeleteRole = async (p: RolePerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.deleteRole.text', 'This shared view will be removed for the role. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onClearRole(p.roleId)\n }\n }\n\n const handleShareApply = async () => {\n if (mode.type !== 'share') return\n setError(null)\n try {\n await onSave({ name: mode.perspectiveName, isDefault: mode.perspectiveIsDefault, applyToRoles: shareRoles, setRoleDefault: shareSetDefault, perspectiveId: mode.perspectiveId })\n setSharedIds((prev) => new Set([...prev, mode.perspectiveId]))\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to share view')\n }\n }\n\n const toggleShareRole = (roleId: string) => {\n setShareRoles((prev) => {\n const next = new Set(prev)\n if (next.has(roleId)) next.delete(roleId)\n else next.add(roleId)\n return Array.from(next)\n })\n }\n\n if (!open) return null\n\n const isNew = mode.type === 'new'\n const isShare = mode.type === 'share'\n\n return (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"fixed right-0 top-0 h-full w-full sm:w-80 bg-background shadow-xl border-l flex flex-col\">\n <div className=\"flex items-center p-4 border-b\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n className=\"h-auto -ml-2 gap-2 px-2 text-lg font-semibold hover:bg-transparent\"\n aria-label={t('ui.perspectives.close', 'Close')}\n >\n <ArrowLeft className=\"size-5\" />\n {t('ui.perspectives.title', 'Views')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n {/* Saved views as chips */}\n <section className=\"p-4 space-y-3\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{t('ui.perspectives.savedViews.title', 'Saved views')}</h3>\n {loading ? <Spinner size=\"sm\" /> : null}\n <div className=\"flex flex-wrap gap-1.5\">\n {/* + New chip */}\n <Button\n type=\"button\"\n variant={isNew ? 'secondary' : 'outline'}\n size=\"sm\"\n className={`h-8 px-3 py-1.5 text-sm ${!isNew ? 'border-dashed' : ''}`}\n onClick={startNewMode}\n >\n <Plus className=\"size-3 mr-1\" />\n {t('ui.perspectives.savedViews.new', 'New')}\n </Button>\n {(perspectives ?? emptyArray).map((p) => {\n const isActive = activePerspectiveId === p.id\n const deleting = deletingIds.includes(p.id)\n const isShared = sharedIds.has(p.id)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"personal\"\n isActive={isActive}\n disabled={deleting}\n isShared={isShared}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={deleting}\n onActivate={() => { onActivatePerspective(p, 'personal'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onRenameStart={() => { setRenamingId(p.id); setRenameValue(p.name) }}\n onClone={() => void handleClone(p)}\n onShareStart={() => startShareMode(p)}\n onDelete={() => void handleDelete(p)}\n />\n )\n })}\n {rolePerspectives\n .filter((rp) => {\n const rpName = rp.name.trim()\n return !perspectives.some((pp) => pp.name.trim() === rpName)\n })\n .map((p) => {\n const isActive = activePerspectiveId === p.id\n const clearing = roleClearingIds.includes(p.roleId)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"role\"\n isActive={isActive}\n disabled={clearing}\n isShared={false}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={clearing}\n onActivate={() => { onActivatePerspective(p, 'role'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onClone={() => void handleClone(p)}\n onDelete={() => void handleDeleteRole(p)}\n />\n )\n })}\n </div>\n\n {/* Inline form slot \u2014 shared between New and Share modes */}\n {isNew ? (\n <NewViewForm\n name={name}\n onNameChange={setName}\n onSubmit={() => void handleSaveNew()}\n onCancel={resetMode}\n saving={saving}\n />\n ) : null}\n\n {isShare ? (\n <ShareForm\n roles={roles}\n shareRoles={shareRoles}\n shareSetDefault={shareSetDefault}\n onToggleRole={toggleShareRole}\n onToggleSetDefault={setShareSetDefault}\n onApply={() => void handleShareApply()}\n onCancel={resetMode}\n />\n ) : null}\n\n {apiWarning ? (\n <Alert variant=\"warning\" className=\"text-xs\">{apiWarning}</Alert>\n ) : null}\n {error ? <div className=\"text-sm text-status-error-text\">{error}</div> : null}\n </section>\n\n {/* Set as default \u2014 separated from chips (border-t) and from columns (ColumnList owns border-t) */}\n <div className=\"flex items-center px-4 mt-2 pt-2 pb-2\">\n <label className=\"inline-flex items-center gap-2 text-sm leading-none\">\n <Checkbox\n checked={isDefault}\n onCheckedChange={(checked) => {\n setIsDefault(checked === true)\n if (isDefaultUserChangeRef.current) {\n scheduleAutosave()\n }\n }}\n />\n {t('ui.perspectives.form.makeDefault', 'Set as default')}\n </label>\n </div>\n\n <ColumnChooserSection\n availableColumns={availableColumns}\n visibleColumnKeys={visibleColumnKeys}\n columnOrder={columnOrder}\n onToggleColumn={handleToggleColumnWithAutosave}\n onReorderColumns={handleReorderColumnsWithAutosave}\n dndContextId=\"perspective-columns\"\n />\n </div>\n </div>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ArrowLeft, Plus } from 'lucide-react'\nimport { Alert } from '../primitives/alert'\nimport { Button } from '../primitives/button'\nimport { Checkbox } from '../primitives/checkbox'\nimport { Spinner } from '../primitives/spinner'\nimport { useConfirmDialog } from './confirm-dialog'\nimport { flash } from './FlashMessages'\nimport { surfaceRecordConflict } from './conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n PerspectiveDto,\n PerspectiveSettings,\n RolePerspectiveDto,\n} from '@open-mercato/shared/modules/perspectives/types'\nimport { ViewChip } from './views/ViewChip'\nimport { NewViewForm } from './views/NewViewForm'\nimport { ShareForm } from './views/ShareForm'\nimport { type SidebarMode } from './views/types'\nimport { ColumnChooserSection, type ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type { ColumnChooserField } from './columns/ColumnChooserPanel'\n\nexport type PerspectiveSidebarProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n loading: boolean\n perspectives: PerspectiveDto[]\n rolePerspectives: RolePerspectiveDto[]\n roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>\n activePerspectiveId: string | null\n onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void\n onDeletePerspective: (perspectiveId: string) => Promise<void>\n onClearRole: (perspective: RolePerspectiveDto) => Promise<void>\n onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>\n canApplyToRoles: boolean\n availableColumns: ColumnChooserField[]\n visibleColumnKeys: string[]\n columnOrder: string[]\n onToggleColumn: (key: string) => void\n onReorderColumns: (orderedIds: string[]) => void\n saving: boolean\n deletingIds: string[]\n roleClearingIds: string[]\n apiWarning?: string | null\n}\n\nconst emptyArray: any[] = []\n\nexport function PerspectiveSidebar({\n open,\n onOpenChange,\n loading,\n perspectives,\n rolePerspectives,\n roles,\n activePerspectiveId,\n onActivatePerspective,\n onDeletePerspective,\n onClearRole,\n onSave,\n canApplyToRoles,\n availableColumns,\n visibleColumnKeys,\n columnOrder,\n onToggleColumn,\n onReorderColumns,\n saving,\n deletingIds,\n roleClearingIds,\n apiWarning,\n}: PerspectiveSidebarProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n if (!open) return\n if (typeof document === 'undefined') return\n document.body.dataset.columnChooserOpen = 'true'\n return () => {\n delete document.body.dataset.columnChooserOpen\n }\n }, [open])\n\n function perspectiveLabel(p: PerspectiveDto | RolePerspectiveDto) {\n return p.name.trim().length ? p.name : t('ui.perspectives.untitled', 'Untitled view')\n }\n\n const [name, setName] = React.useState('')\n const [isDefault, setIsDefault] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [mode, setMode] = React.useState<SidebarMode>({ type: 'idle' })\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameValue, setRenameValue] = React.useState('')\n const [shareRoles, setShareRoles] = React.useState<string[]>([])\n const [shareSetDefault, setShareSetDefault] = React.useState(false)\n const [sharedIds, setSharedIds] = React.useState<Set<string>>(new Set())\n const [pendingCloneBaselineIds, setPendingCloneBaselineIds] = React.useState<Set<string> | null>(null)\n\n const autosaveRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const isDefaultUserChangeRef = React.useRef(false)\n const isDefaultRef = React.useRef(isDefault)\n isDefaultRef.current = isDefault\n\n const onSaveRef = React.useRef(onSave)\n onSaveRef.current = onSave\n\n const perspectivesRef = React.useRef(perspectives)\n perspectivesRef.current = perspectives\n\n const flushAutosave = React.useCallback(() => {\n if (autosaveRef.current) {\n clearTimeout(autosaveRef.current)\n autosaveRef.current = null\n }\n }, [])\n\n const scheduleAutosave = React.useCallback(() => {\n if (!activePerspectiveId || mode.type === 'new') return\n const activePersonal = perspectivesRef.current.find((p) => p.id === activePerspectiveId)\n if (!activePersonal) return\n flushAutosave()\n const targetId = activePersonal.id\n const targetName = activePersonal.name\n autosaveRef.current = setTimeout(async () => {\n autosaveRef.current = null\n try {\n await onSaveRef.current({\n name: targetName,\n isDefault: isDefaultRef.current,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: targetId,\n })\n flash(t('ui.perspectives.autosave.success', 'View saved'), 'success')\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n flash(t('ui.perspectives.autosave.error', 'Failed to save view'), 'error')\n }\n }, 400)\n }, [activePerspectiveId, mode.type, flushAutosave, t])\n\n React.useEffect(() => {\n return () => { flushAutosave() }\n }, [flushAutosave])\n\n const resetMode = () => {\n setMode({ type: 'idle' })\n setName('')\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startNewMode = () => {\n setMode({ type: 'new' })\n setName('')\n setIsDefault(false)\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n const startShareMode = (p: PerspectiveDto) => {\n setMode({ type: 'share', perspectiveId: p.id, perspectiveName: p.name, perspectiveIsDefault: p.isDefault })\n setName('')\n setRenamingId(null)\n setShareRoles([])\n setShareSetDefault(false)\n }\n\n React.useEffect(() => {\n if (!open) {\n flushAutosave()\n setError(null)\n resetMode()\n setRenamingId(null)\n setSharedIds(new Set())\n setPendingCloneBaselineIds(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n const active = perspectives.find((p) => p.id === activePerspectiveId)\n ?? rolePerspectives.find((p) => p.id === activePerspectiveId)\n isDefaultUserChangeRef.current = false\n if (active) {\n setIsDefault(active.isDefault)\n } else {\n setIsDefault(false)\n }\n requestAnimationFrame(() => { isDefaultUserChangeRef.current = true })\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, activePerspectiveId])\n\n const handleSaveNew = async () => {\n setError(null)\n try {\n await onSave({ name: name.trim(), isDefault, applyToRoles: [], setRoleDefault: false, perspectiveId: null })\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to save view')\n }\n }\n\n const handleToggleColumnWithAutosave = React.useCallback((key: string) => {\n onToggleColumn(key)\n scheduleAutosave()\n }, [onToggleColumn, scheduleAutosave])\n\n const handleReorderColumnsWithAutosave = React.useCallback((orderedIds: string[]) => {\n onReorderColumns(orderedIds)\n scheduleAutosave()\n }, [onReorderColumns, scheduleAutosave])\n\n const handleRename = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const trimmed = renameValue.trim()\n if (!trimmed) {\n setRenamingId(null)\n return\n }\n const conflict = perspectivesRef.current.some(\n (item) => item.id !== p.id && item.name.trim() === trimmed,\n )\n if (conflict) {\n flash(t('ui.perspectives.error.nameExists', 'View with this name already exists'), 'error')\n return\n }\n setError(null)\n try {\n await onSave({\n name: trimmed,\n isDefault: p.isDefault,\n applyToRoles: [],\n setRoleDefault: false,\n perspectiveId: p.id,\n })\n setRenamingId(null)\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to rename view')\n }\n }\n\n const handleClone = async (p: PerspectiveDto | RolePerspectiveDto) => {\n const originalName = perspectiveLabel(p)\n setError(null)\n const baseline = new Set(perspectivesRef.current.map((item) => item.id))\n const originalSettingsKey = JSON.stringify(p.settings ?? {})\n const sharedRoleIds = Array.from(\n new Set(\n rolePerspectives\n .filter((rp) => JSON.stringify(rp.settings ?? {}) === originalSettingsKey)\n .map((rp) => rp.roleId),\n ),\n )\n const buildCloneName = (n: number) =>\n n <= 1 ? `${originalName} (copy)` : `${originalName} (copy ${n})`\n const existingNames = new Set(\n perspectivesRef.current.map((item) => item.name.trim()),\n )\n let counter = 1\n while (existingNames.has(buildCloneName(counter))) {\n counter += 1\n }\n const clonedSettings = structuredClone(p.settings)\n const MAX_ATTEMPTS = 50\n let lastErr: unknown = null\n for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n try {\n await onSave({\n name: buildCloneName(counter),\n isDefault: false,\n applyToRoles: sharedRoleIds,\n setRoleDefault: false,\n perspectiveId: null,\n settings: clonedSettings,\n })\n setPendingCloneBaselineIds(baseline)\n return\n } catch (err: unknown) {\n const errMessage = err instanceof Error ? err.message : ''\n const msg = errMessage.toLowerCase()\n const isDuplicate = msg.includes('duplicate key') || msg.includes('unique constraint')\n if (!isDuplicate) {\n if (surfaceRecordConflict(err, t)) return\n setError(errMessage || 'Failed to clone view')\n return\n }\n lastErr = err\n counter += 1\n }\n }\n if (surfaceRecordConflict(lastErr, t)) return\n setError(lastErr instanceof Error ? lastErr.message : 'Failed to clone view')\n }\n\n React.useEffect(() => {\n if (!pendingCloneBaselineIds) return\n const created = perspectives.find((item) => !pendingCloneBaselineIds.has(item.id))\n if (!created) return\n setRenamingId(created.id)\n setRenameValue(created.name)\n setPendingCloneBaselineIds(null)\n }, [perspectives, pendingCloneBaselineIds])\n\n const handleDelete = async (p: PerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.delete.text', 'This view will be removed for you and all shared roles. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onDeletePerspective(p.id)\n }\n }\n\n const handleDeleteRole = async (p: RolePerspectiveDto) => {\n const confirmed = await confirm({\n title: t('ui.perspectives.delete.title', 'Delete \"{name}\"?', { name: perspectiveLabel(p) }),\n text: t('ui.perspectives.deleteRole.text', 'This shared view will be removed for the role. This cannot be undone.'),\n confirmText: t('common.delete', 'Delete'),\n variant: 'destructive',\n })\n if (confirmed) {\n await onClearRole(p)\n }\n }\n\n const handleShareApply = async () => {\n if (mode.type !== 'share') return\n setError(null)\n try {\n await onSave({ name: mode.perspectiveName, isDefault: mode.perspectiveIsDefault, applyToRoles: shareRoles, setRoleDefault: shareSetDefault, perspectiveId: mode.perspectiveId })\n setSharedIds((prev) => new Set([...prev, mode.perspectiveId]))\n resetMode()\n } catch (err: unknown) {\n if (surfaceRecordConflict(err, t)) return\n setError(err instanceof Error ? err.message : 'Failed to share view')\n }\n }\n\n const toggleShareRole = (roleId: string) => {\n setShareRoles((prev) => {\n const next = new Set(prev)\n if (next.has(roleId)) next.delete(roleId)\n else next.add(roleId)\n return Array.from(next)\n })\n }\n\n if (!open) return null\n\n const isNew = mode.type === 'new'\n const isShare = mode.type === 'share'\n\n return (\n <div className=\"fixed inset-0 z-modal\">\n <div className=\"absolute inset-0 bg-black/20\" onClick={() => onOpenChange(false)} role=\"presentation\" />\n <div className=\"fixed right-0 top-0 h-full w-full sm:w-80 bg-background shadow-xl border-l flex flex-col\">\n <div className=\"flex items-center p-4 border-b\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n className=\"h-auto -ml-2 gap-2 px-2 text-lg font-semibold hover:bg-transparent\"\n aria-label={t('ui.perspectives.close', 'Close')}\n >\n <ArrowLeft className=\"size-5\" />\n {t('ui.perspectives.title', 'Views')}\n </Button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n {/* Saved views as chips */}\n <section className=\"p-4 space-y-3\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">{t('ui.perspectives.savedViews.title', 'Saved views')}</h3>\n {loading ? <Spinner size=\"sm\" /> : null}\n <div className=\"flex flex-wrap gap-1.5\">\n {/* + New chip */}\n <Button\n type=\"button\"\n variant={isNew ? 'secondary' : 'outline'}\n size=\"sm\"\n className={`h-8 px-3 py-1.5 text-sm ${!isNew ? 'border-dashed' : ''}`}\n onClick={startNewMode}\n >\n <Plus className=\"size-3 mr-1\" />\n {t('ui.perspectives.savedViews.new', 'New')}\n </Button>\n {(perspectives ?? emptyArray).map((p) => {\n const isActive = activePerspectiveId === p.id\n const deleting = deletingIds.includes(p.id)\n const isShared = sharedIds.has(p.id)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"personal\"\n isActive={isActive}\n disabled={deleting}\n isShared={isShared}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={deleting}\n onActivate={() => { onActivatePerspective(p, 'personal'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onRenameStart={() => { setRenamingId(p.id); setRenameValue(p.name) }}\n onClone={() => void handleClone(p)}\n onShareStart={() => startShareMode(p)}\n onDelete={() => void handleDelete(p)}\n />\n )\n })}\n {rolePerspectives\n .filter((rp) => {\n const rpName = rp.name.trim()\n return !perspectives.some((pp) => pp.name.trim() === rpName)\n })\n .map((p) => {\n const isActive = activePerspectiveId === p.id\n const clearing = roleClearingIds.includes(p.roleId)\n return (\n <ViewChip\n key={p.id}\n id={p.id}\n label={perspectiveLabel(p)}\n kind=\"role\"\n isActive={isActive}\n disabled={clearing}\n isShared={false}\n isRenaming={renamingId === p.id}\n renameValue={renameValue}\n canApplyToRoles={canApplyToRoles}\n deleting={clearing}\n onActivate={() => { onActivatePerspective(p, 'role'); resetMode() }}\n onRenameValueChange={setRenameValue}\n onRenameConfirm={() => void handleRename(p)}\n onRenameCancel={() => setRenamingId(null)}\n onClone={() => void handleClone(p)}\n onDelete={() => void handleDeleteRole(p)}\n />\n )\n })}\n </div>\n\n {/* Inline form slot \u2014 shared between New and Share modes */}\n {isNew ? (\n <NewViewForm\n name={name}\n onNameChange={setName}\n onSubmit={() => void handleSaveNew()}\n onCancel={resetMode}\n saving={saving}\n />\n ) : null}\n\n {isShare ? (\n <ShareForm\n roles={roles}\n shareRoles={shareRoles}\n shareSetDefault={shareSetDefault}\n onToggleRole={toggleShareRole}\n onToggleSetDefault={setShareSetDefault}\n onApply={() => void handleShareApply()}\n onCancel={resetMode}\n />\n ) : null}\n\n {apiWarning ? (\n <Alert variant=\"warning\" className=\"text-xs\">{apiWarning}</Alert>\n ) : null}\n {error ? <div className=\"text-sm text-status-error-text\">{error}</div> : null}\n </section>\n\n {/* Set as default \u2014 separated from chips (border-t) and from columns (ColumnList owns border-t) */}\n <div className=\"flex items-center px-4 mt-2 pt-2 pb-2\">\n <label className=\"inline-flex items-center gap-2 text-sm leading-none\">\n <Checkbox\n checked={isDefault}\n onCheckedChange={(checked) => {\n setIsDefault(checked === true)\n if (isDefaultUserChangeRef.current) {\n scheduleAutosave()\n }\n }}\n />\n {t('ui.perspectives.form.makeDefault', 'Set as default')}\n </label>\n </div>\n\n <ColumnChooserSection\n availableColumns={availableColumns}\n visibleColumnKeys={visibleColumnKeys}\n columnOrder={columnOrder}\n onToggleColumn={handleToggleColumnWithAutosave}\n onReorderColumns={handleReorderColumnsWithAutosave}\n dndContextId=\"perspective-columns\"\n />\n </div>\n </div>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6256.1.9fc16aedc4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -155,13 +155,13 @@
|
|
|
155
155
|
"remark-gfm": "^4.0.1"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
158
|
+
"@open-mercato/shared": "0.6.6-develop.6256.1.9fc16aedc4",
|
|
159
159
|
"react": ">=18.0.0",
|
|
160
160
|
"react-dom": ">=18.0.0",
|
|
161
161
|
"react-is": ">=18.0.0"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|
|
164
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
164
|
+
"@open-mercato/shared": "0.6.6-develop.6256.1.9fc16aedc4",
|
|
165
165
|
"@testing-library/dom": "^10.4.1",
|
|
166
166
|
"@testing-library/jest-dom": "^6.9.1",
|
|
167
167
|
"@testing-library/react": "^16.3.1",
|
|
@@ -1184,6 +1184,7 @@ export function DataTable<T>({
|
|
|
1184
1184
|
perspectives: [],
|
|
1185
1185
|
defaultPerspectiveId: null,
|
|
1186
1186
|
rolePerspectives: [],
|
|
1187
|
+
manageableRolePerspectives: [],
|
|
1187
1188
|
roles: [],
|
|
1188
1189
|
canApplyToRoles: false,
|
|
1189
1190
|
}
|
|
@@ -1716,6 +1717,10 @@ export function DataTable<T>({
|
|
|
1716
1717
|
}
|
|
1717
1718
|
|
|
1718
1719
|
const perspectiveQueryKey: [string, string | null] = ['table-perspectives', perspectiveTableId]
|
|
1720
|
+
const rolePerspectivesForLocking = React.useMemo(
|
|
1721
|
+
() => perspectiveData?.manageableRolePerspectives ?? perspectiveData?.rolePerspectives ?? [],
|
|
1722
|
+
[perspectiveData],
|
|
1723
|
+
)
|
|
1719
1724
|
|
|
1720
1725
|
type PerspectiveMutationContext = {
|
|
1721
1726
|
formId: string
|
|
@@ -1740,6 +1745,21 @@ export function DataTable<T>({
|
|
|
1740
1745
|
const savePerspectiveMutation = useMutation<PerspectiveSaveResponse, Error, SavePerspectivePayload>({
|
|
1741
1746
|
mutationFn: async (input) => {
|
|
1742
1747
|
if (!perspectiveTableId) throw new Error('Missing table id')
|
|
1748
|
+
const roleExpectedUpdatedAtByRoleId: Record<string, string> = {}
|
|
1749
|
+
const roleExpectedUpdatedAtByPerspectiveId: Record<string, string> = {}
|
|
1750
|
+
for (const roleId of input.applyToRoles) {
|
|
1751
|
+
const rolePerspectives = rolePerspectivesForLocking.filter((p) => p.roleId === roleId)
|
|
1752
|
+
const matching = rolePerspectives.find((p) => p.name.trim() === input.name.trim()) ?? null
|
|
1753
|
+
const defaultPerspective = input.setRoleDefault
|
|
1754
|
+
? rolePerspectives.find((p) => p.isDefault) ?? null
|
|
1755
|
+
: null
|
|
1756
|
+
for (const candidate of [matching, defaultPerspective]) {
|
|
1757
|
+
if (!candidate?.updatedAt) continue
|
|
1758
|
+
roleExpectedUpdatedAtByPerspectiveId[candidate.id] = candidate.updatedAt
|
|
1759
|
+
}
|
|
1760
|
+
const roleFallback = matching ?? defaultPerspective
|
|
1761
|
+
if (roleFallback?.updatedAt) roleExpectedUpdatedAtByRoleId[roleId] = roleFallback.updatedAt
|
|
1762
|
+
}
|
|
1743
1763
|
const payload = {
|
|
1744
1764
|
perspectiveId: input.perspectiveId ?? undefined,
|
|
1745
1765
|
name: input.name,
|
|
@@ -1747,6 +1767,12 @@ export function DataTable<T>({
|
|
|
1747
1767
|
isDefault: input.isDefault,
|
|
1748
1768
|
applyToRoles: input.applyToRoles,
|
|
1749
1769
|
setRoleDefault: input.setRoleDefault,
|
|
1770
|
+
...(Object.keys(roleExpectedUpdatedAtByRoleId).length > 0
|
|
1771
|
+
? { roleExpectedUpdatedAtByRoleId }
|
|
1772
|
+
: {}),
|
|
1773
|
+
...(Object.keys(roleExpectedUpdatedAtByPerspectiveId).length > 0
|
|
1774
|
+
? { roleExpectedUpdatedAtByPerspectiveId }
|
|
1775
|
+
: {}),
|
|
1750
1776
|
}
|
|
1751
1777
|
if (process.env.NODE_ENV !== 'production') {
|
|
1752
1778
|
// eslint-disable-next-line no-console
|
|
@@ -1819,11 +1845,15 @@ export function DataTable<T>({
|
|
|
1819
1845
|
const deletePerspectiveMutation = useMutation<void, Error, { perspectiveId: string }>({
|
|
1820
1846
|
mutationFn: async ({ perspectiveId }) => {
|
|
1821
1847
|
if (!perspectiveTableId) throw new Error('Missing table id')
|
|
1848
|
+
const existing = perspectiveData?.perspectives.find((p) => p.id === perspectiveId) ?? null
|
|
1822
1849
|
await runPerspectiveMutation({
|
|
1823
1850
|
operation: async () => {
|
|
1824
|
-
const call = await
|
|
1825
|
-
|
|
1826
|
-
|
|
1851
|
+
const call = await withScopedApiRequestHeaders(
|
|
1852
|
+
buildOptimisticLockHeader(existing?.updatedAt ?? null),
|
|
1853
|
+
() => apiCall(
|
|
1854
|
+
`/api/perspectives/${encodeURIComponent(perspectiveTableId)}/${encodeURIComponent(perspectiveId)}`,
|
|
1855
|
+
{ method: 'DELETE' },
|
|
1856
|
+
),
|
|
1827
1857
|
)
|
|
1828
1858
|
if (call.status === 404) throw new Error(t('ui.dataTable.perspectives.error.apiUnavailable', 'Perspectives API is not available. Run `yarn generate` and restart the dev server.'))
|
|
1829
1859
|
if (!call.ok) {
|
|
@@ -1856,16 +1886,38 @@ export function DataTable<T>({
|
|
|
1856
1886
|
initialPerspectiveAppliedRef.current = false
|
|
1857
1887
|
}
|
|
1858
1888
|
},
|
|
1889
|
+
onError: () => {
|
|
1890
|
+
// Conflict surfacing is handled inside `runPerspectiveMutation`
|
|
1891
|
+
// (surfaceRecordConflict); only the cache refresh remains here.
|
|
1892
|
+
if (perspectiveTableId) {
|
|
1893
|
+
void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey })
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1859
1896
|
})
|
|
1860
1897
|
|
|
1861
|
-
const clearRoleMutation = useMutation<void, Error, {
|
|
1862
|
-
|
|
1898
|
+
const clearRoleMutation = useMutation<void, Error, {
|
|
1899
|
+
roleId: string
|
|
1900
|
+
updatedAt?: string | null
|
|
1901
|
+
expectedUpdatedAtByPerspectiveId?: Record<string, string>
|
|
1902
|
+
}>({
|
|
1903
|
+
mutationFn: async ({ roleId, updatedAt, expectedUpdatedAtByPerspectiveId }) => {
|
|
1863
1904
|
if (!perspectiveTableId) throw new Error('Missing table id')
|
|
1905
|
+
const hasPerRowVersions = expectedUpdatedAtByPerspectiveId
|
|
1906
|
+
&& Object.keys(expectedUpdatedAtByPerspectiveId).length > 0
|
|
1864
1907
|
await runPerspectiveMutation({
|
|
1865
1908
|
operation: async () => {
|
|
1866
|
-
const call = await
|
|
1867
|
-
|
|
1868
|
-
|
|
1909
|
+
const call = await withScopedApiRequestHeaders(
|
|
1910
|
+
hasPerRowVersions ? {} : buildOptimisticLockHeader(updatedAt ?? null),
|
|
1911
|
+
() => apiCall(
|
|
1912
|
+
`/api/perspectives/${encodeURIComponent(perspectiveTableId)}/roles/${encodeURIComponent(roleId)}`,
|
|
1913
|
+
hasPerRowVersions
|
|
1914
|
+
? {
|
|
1915
|
+
method: 'DELETE',
|
|
1916
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1917
|
+
body: JSON.stringify({ roleExpectedUpdatedAtByPerspectiveId: expectedUpdatedAtByPerspectiveId }),
|
|
1918
|
+
}
|
|
1919
|
+
: { method: 'DELETE' },
|
|
1920
|
+
),
|
|
1869
1921
|
)
|
|
1870
1922
|
if (call.status === 404) throw new Error(t('ui.dataTable.perspectives.error.apiUnavailable', 'Perspectives API is not available. Run `yarn generate` and restart the dev server.'))
|
|
1871
1923
|
if (!call.ok) {
|
|
@@ -1898,6 +1950,13 @@ export function DataTable<T>({
|
|
|
1898
1950
|
}
|
|
1899
1951
|
}
|
|
1900
1952
|
},
|
|
1953
|
+
onError: () => {
|
|
1954
|
+
// Conflict surfacing is handled inside `runPerspectiveMutation`
|
|
1955
|
+
// (surfaceRecordConflict); only the cache refresh remains here.
|
|
1956
|
+
if (perspectiveTableId) {
|
|
1957
|
+
void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey })
|
|
1958
|
+
}
|
|
1959
|
+
},
|
|
1901
1960
|
})
|
|
1902
1961
|
|
|
1903
1962
|
const handlePerspectiveActivate = React.useCallback((item: PerspectiveDto | RolePerspectiveDto, _source?: 'personal' | 'role') => {
|
|
@@ -1920,9 +1979,18 @@ export function DataTable<T>({
|
|
|
1920
1979
|
await deletePerspectiveMutation.mutateAsync({ perspectiveId })
|
|
1921
1980
|
}, [deletePerspectiveMutation])
|
|
1922
1981
|
|
|
1923
|
-
const handleClearRole = React.useCallback(async (
|
|
1924
|
-
|
|
1925
|
-
|
|
1982
|
+
const handleClearRole = React.useCallback(async (perspective: RolePerspectiveDto) => {
|
|
1983
|
+
const expectedUpdatedAtByPerspectiveId = Object.fromEntries(
|
|
1984
|
+
rolePerspectivesForLocking
|
|
1985
|
+
.filter((item) => item.roleId === perspective.roleId && item.updatedAt)
|
|
1986
|
+
.map((item) => [item.id, item.updatedAt as string]),
|
|
1987
|
+
)
|
|
1988
|
+
await clearRoleMutation.mutateAsync({
|
|
1989
|
+
roleId: perspective.roleId,
|
|
1990
|
+
updatedAt: perspective.updatedAt ?? null,
|
|
1991
|
+
expectedUpdatedAtByPerspectiveId,
|
|
1992
|
+
})
|
|
1993
|
+
}, [clearRoleMutation, rolePerspectivesForLocking])
|
|
1926
1994
|
|
|
1927
1995
|
const handleColumnChooserToggle = React.useCallback((key: string) => {
|
|
1928
1996
|
const column = table.getColumn(key)
|
|
@@ -32,7 +32,7 @@ export type PerspectiveSidebarProps = {
|
|
|
32
32
|
activePerspectiveId: string | null
|
|
33
33
|
onActivatePerspective: (perspective: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void
|
|
34
34
|
onDeletePerspective: (perspectiveId: string) => Promise<void>
|
|
35
|
-
onClearRole: (
|
|
35
|
+
onClearRole: (perspective: RolePerspectiveDto) => Promise<void>
|
|
36
36
|
onSave: (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => Promise<void>
|
|
37
37
|
canApplyToRoles: boolean
|
|
38
38
|
availableColumns: ColumnChooserField[]
|
|
@@ -327,7 +327,7 @@ export function PerspectiveSidebar({
|
|
|
327
327
|
variant: 'destructive',
|
|
328
328
|
})
|
|
329
329
|
if (confirmed) {
|
|
330
|
-
await onClearRole(p
|
|
330
|
+
await onClearRole(p)
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
@@ -6,7 +6,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
|
6
6
|
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
7
7
|
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
|
|
8
8
|
import { OPTIMISTIC_LOCK_HEADER_NAME } from '@open-mercato/shared/lib/crud/optimistic-lock-headers'
|
|
9
|
-
import type { PerspectivesIndexResponse } from '@open-mercato/shared/modules/perspectives/types'
|
|
9
|
+
import type { PerspectivesIndexResponse, RolePerspectiveDto } from '@open-mercato/shared/modules/perspectives/types'
|
|
10
10
|
|
|
11
11
|
jest.mock('next/navigation', () => ({
|
|
12
12
|
useRouter: () => ({ push: jest.fn(), replace: jest.fn(), prefetch: jest.fn() }),
|
|
@@ -82,7 +82,20 @@ type SidebarProps = {
|
|
|
82
82
|
settings?: unknown
|
|
83
83
|
}) => void | Promise<void>
|
|
84
84
|
onDeletePerspective: (perspectiveId: string) => void | Promise<void>
|
|
85
|
-
onClearRole: (
|
|
85
|
+
onClearRole: (perspective: RolePerspectiveDto) => void | Promise<void>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const CLEAR_ROLE_PERSPECTIVE: RolePerspectiveDto = {
|
|
89
|
+
id: 'role-persp-1',
|
|
90
|
+
name: 'Role view',
|
|
91
|
+
tableId: 'test-table',
|
|
92
|
+
settings: {} as RolePerspectiveDto['settings'],
|
|
93
|
+
isDefault: false,
|
|
94
|
+
createdAt: 'now',
|
|
95
|
+
updatedAt: '2026-06-19T00:00:00.000Z',
|
|
96
|
+
roleId: 'role-1',
|
|
97
|
+
tenantId: null,
|
|
98
|
+
organizationId: null,
|
|
86
99
|
}
|
|
87
100
|
|
|
88
101
|
// Replace the heavy drawer UI with a stub exposing the three write handlers as
|
|
@@ -126,7 +139,7 @@ jest.mock('../PerspectiveSidebar', () => ({
|
|
|
126
139
|
type="button"
|
|
127
140
|
data-testid="clear-role"
|
|
128
141
|
onClick={() => {
|
|
129
|
-
void props.onClearRole(
|
|
142
|
+
void props.onClearRole(CLEAR_ROLE_PERSPECTIVE)
|
|
130
143
|
}}
|
|
131
144
|
>
|
|
132
145
|
clear
|