fix(admin): keep list state in the URL and browser storage

- listState.ts stores each list's query (search, sort, dir, page, per_page,
  filter[name]) under summer-admin.list:<controller>; an empty query is
  never stored, malformed stored values are dropped like a hand-edited URL
- ListView restores the stored query into the URL before its first list
  request when entered without list params; a URL with list params wins
- a 422 forgets the stored query; an out-of-range page clamps to last_page
- logout clears every stored list query
- check-phase10.sh allows browser storage in useSidebar.ts and listState.ts
- list smoke tests start from a clean list state
- rebuilt modules/boardwalk/dist
This commit is contained in:
2026-10-09 21:04:00 +02:00
parent bd8626e5fc
commit 9cac45c389
10 changed files with 186 additions and 18 deletions

View File

@@ -17,13 +17,17 @@ import PartialHost from '../components/partial/PartialHost.vue'
import Button from '../components/ui/Button.vue'
import ConfirmDialog from '../components/ui/ConfirmDialog.vue'
import { useConfirm } from '../components/ui/confirm'
import { readListState, writeListState } from '../state/listState'
import { activeEntry } from '../state/useNavigation'
import { showToast } from '../state/useToasts'
// Schema-driven list (design screen 3, D-12, D-13, D-14, D-22). Search, sort,
// page, per_page and filters live in the URL query and every change is a
// router replace; any change clears the selection. The view is keyed by
// route path, so it serves one controller.
// route path, so it serves one controller. Every query the list loads is also
// kept per list in browser storage (listState.ts, D-01): entered without list
// params, the list restores the stored query into the URL before its first
// list request; a URL with list params wins and overwrites the stored query.
const SEARCH_DEBOUNCE = 300
const route = useRoute()
@@ -112,9 +116,14 @@ function rowLink(row: AdminRecord): string | null {
}
function replaceQuery(next: ListQuery): void {
// Stored first, so clearing every control stores the empty query and the
// following run loads the unfiltered list instead of restoring.
writeListState(controllerId, next)
void router.replace({ path: route.path, query: toListQuery(next) })
}
const EMPTY_QUERY_KEY = listQueryKey({})
let generation = 0
async function loadList(): Promise<void> {
@@ -129,6 +138,16 @@ async function loadList(): Promise<void> {
meta.value = result.data?.meta ?? null
listFailed.value = !result.data
loading.value = false
if (result.response?.status === 422) {
// A stale stored filter or sort never sticks.
writeListState(controllerId, {})
return
}
const lastPage = meta.value?.last_page ?? 0
if (lastPage >= 1 && (meta.value?.page ?? 0) > lastPage) {
// Records were removed since the query was stored or shared.
replaceQuery({ ...query.value, page: lastPage })
}
}
async function load(): Promise<void> {
@@ -145,7 +164,18 @@ void load()
watch(
() => listQueryKey(query.value),
() => {
(key) => {
// Entered without list params (navigation, breadcrumb, a form's back,
// cancel, save or delete, or the same list's link while it is filtered):
// restore the stored query into the URL and let this watch load it.
if (key === EMPTY_QUERY_KEY) {
const stored = readListState(controllerId)
if (listQueryKey(stored) !== EMPTY_QUERY_KEY) {
void router.replace({ path: route.path, query: toListQuery(stored) })
return
}
}
writeListState(controllerId, query.value)
selected.value = []
searchText.value = query.value.search ?? ''
void loadList()