feat(views): editable saved-view detail — removable filter chips + undo + save-current (S7)
The Personal-views manager's detail pane becomes editable instead of read-only: - Captured filters render as REMOVABLE chips. Removing one prunes the set and writes it straight back to the stored view (PATCH filters), via the same live-row builders (useActiveFilters / buildPlexActiveFilters) that already compute the pruned snapshot — so the modal and the live chip row can't drift. The prune is wrapped in useUndoable + the shared UndoToolbar, so undo/redo re-persist the same way. The undoable draft is the single source of truth for the chip row, so a chip-prune refetch never clobbers the history. - A "Save current" button beside Apply stores the module's currently-applied filters INTO the view (the live→stored inverse of Apply). Disabled when the view already equals the live filters (it's the active one). On save the draft re-seeds to the saved snapshot and clears the stale undo history. - Rename gains a visible commit affordance: a check button beside the name input that enables once the name changes (Enter and blur still commit). Provider gains two methods mirroring `rename`: updateFilters(id, filters) and saveCurrent(id). One shared provider serves feed + plex, so both get it. PATCH filters was already supported end-to-end — FE-only. New i18n: views.saveCurrent / saveCurrentHint / saveCurrentUpToDate (HU+EN). Tests: 5 new in PersonalViewsModal.dom.test.tsx (chip-remove persists, undo re-persists, save-current fires + disables when up-to-date, rename commit).
This commit is contained in:
@@ -103,6 +103,11 @@ export interface SavedViewsContext {
|
||||
clearActive: () => void;
|
||||
create: (name: string) => Promise<boolean>;
|
||||
rename: (id: number, name: string) => Promise<boolean>;
|
||||
/** Overwrite a view's captured filters (used by the modal's removable chips). */
|
||||
updateFilters: (id: number, filters: Record<string, unknown>) => Promise<boolean>;
|
||||
/** Save the module's CURRENTLY-applied filters into a view (the live→stored inverse of `apply`);
|
||||
* resolves to the saved snapshot on success (so the caller can resync its editor) or null. */
|
||||
saveCurrent: (id: number) => Promise<Record<string, unknown> | null>;
|
||||
toggleDefault: (v: SavedView) => Promise<void>;
|
||||
remove: (v: SavedView) => Promise<boolean>;
|
||||
reorder: (nextIds: number[]) => Promise<void>;
|
||||
@@ -258,6 +263,16 @@ export function SavedViewsProvider({ children }: { children: ReactNode }) {
|
||||
if (!next) return false;
|
||||
return runViewMutation(() => api.updateView(id, { name: next }), t("views.actionFailed"));
|
||||
},
|
||||
updateFilters: (id, filters) =>
|
||||
runViewMutation(() => api.updateView(id, { filters }), t("views.actionFailed")),
|
||||
saveCurrent: async (id) => {
|
||||
const filters = cfg.stripForSave(cfg.filters);
|
||||
const ok = await runViewMutation(
|
||||
() => api.updateView(id, { filters }),
|
||||
t("views.actionFailed"),
|
||||
);
|
||||
return ok ? filters : null;
|
||||
},
|
||||
toggleDefault: (v) =>
|
||||
runViewMutation(
|
||||
() => api.updateView(v.id, { is_default: !v.is_default }),
|
||||
|
||||
Reference in New Issue
Block a user