Skip to content

Generalize useAutoSave to side-panel edit forms #317

Description

@froozeify

Generalize useAutoSave to side-panel edit forms

Context

useAutoSave (app/composables/useAutoSave.ts) was introduced in the terminal-payment-support
branch as a generic debounced-patch composable (debounce + merge concurrent field edits + isSaving
flag). It currently has two consumers:

  • app/pages/admin/sales/payment-terminals.vue (description/icon inline fields)
  • app/components/Inventory/InventoryStockControl.vue (stock quantity stepper)

Both are single-field or few-field inline controls. Most of our admin CRUD pages instead use a
different, older pattern: a side-panel UForm with an explicit type="submit" "Enregistrer"
button (and often a separate "Supprimer" button), e.g.:

  • app/pages/admin/inventories/categories/index.vue
  • app/pages/admin/loans/recording-types/index.vue
  • app/pages/admin/loans/categories/index.vue
  • app/pages/admin/sales/payment-modes.vue
  • app/pages/admin/config/activities.vue
  • app/pages/admin/config/permissions.vue

These forms all follow the same shape: select a row → side panel opens with a UForm → edit fields
→ click "Enregistrer" to PATCH/POST → optionally "Supprimer" to delete. That's a lot of duplicated
boilerplate (loading flags, toast wiring, post-vs-patch branching, refresh-the-list-after) for the
same underlying pattern.

Goal

Explore replacing the explicit-submit pattern on these side-panel forms with the same debounced
auto-save UX already used for inline fields — so editing a category/recording-type/payment-mode/etc.
feels the same as editing a stock quantity: type, it saves, no button to remember to click.

Why

  • Consistency: two different save UX patterns exist side by side (auto-save vs. explicit submit)
    for what is conceptually the same "edit a record" interaction. Users shouldn't have to remember
    which pages autosave and which don't.
  • Less boilerplate: updateCategory/updateRecordingType/etc. all hand-roll the same
    loading/toast/create-vs-update branching that useAutoSave already centralizes.
  • This came up because migrating InventoryStockControl.vue to useAutoSave (terminal-payment-support
    branch) made the contrast with the still-manual category form obvious — same underlying "PATCH one
    field" need, two different UX patterns in the app.

What's missing from useAutoSave today

The composable currently assumes a single always-present patch target. To cover side-panel forms
it likely needs:

  1. Create support — side panels create a new record (no uuid yet) before any field can be
    saved. useAutoSave would need a "create on first field, then patch subsequent fields" mode, or
    the caller has to eagerly POST an empty record on panel-open (behavior change to weigh).
  2. Delete action support — a remove(item) alongside save, so the composable can also own the
    loading/toast/refresh wiring for the "Supprimer" button, not just PATCH.
  3. Custom actions — some panels have extra buttons beyond save/delete (e.g. "Voir les articles"
    nav link in categories, "move up/down" elsewhere) — these should stay outside the composable's
    concern, but the API should not get in their way.
  4. Validation gate — several forms validate before submit (UForm's :validate). Auto-save
    needs a way to skip/hold the save while the form is invalid, rather than PATCHing bad data on
    every keystroke.
  5. Per-field vs. whole-object save — inline usages call save(item, {field: value}) per field;
    side-panel forms currently submit the whole object at once. Need to decide whether auto-save
    fires per-field-change (more granular, more requests) or debounces the whole form as one payload.

Suggested approach

  • Prototype on one low-risk page first (inventories/categories is a good candidate: simple
    two-field form, already reviewed in this conversation) before rolling out further.
  • Extend useAutoSave (or introduce a sibling useAutoSaveForm) with remove() and validation-gate
    support rather than bolting create/delete onto the existing single-purpose composable — keep the
    inline-field use case (payment-terminals, stock control) simple and untouched.
  • Once proven, migrate the other side-panel forms listed above one PR at a time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions