feat(qualification-criteria): dnd reordering with whole-list PATCH

The chevron up/down buttons rewrote a single row's display_order,
which didn't actually swap positions since the neighbouring rows kept
their original orders. Replaced with a proper drag-handle (dnd-kit
sortable, matching the waiting-list-manager pattern) backed by a new
POST /admin/qualification-criteria/reorder endpoint that rewrites
display_order = index for every row in a transaction. The service
rejects partial / extraneous id lists so a stale UI can't silently
drop a criterion. Optimistic local-cache update keeps the row in
position during the round-trip; rollback on error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 03:49:17 +02:00
parent 905852b8a5
commit 233129f91a
4 changed files with 228 additions and 59 deletions

View File

@@ -22,6 +22,15 @@ export const updateQualificationCriterionSchema = createQualificationCriterionSc
.omit({ key: true })
.partial();
/**
* Whole-list reorder. The IDs array must cover exactly the port's current
* criteria — the service rejects partial / extraneous IDs to keep the
* resulting display_order contiguous.
*/
export const reorderQualificationCriteriaSchema = z.object({
ids: z.array(z.string().min(1)).min(1),
});
/**
* Per-interest qualification state. Only `confirmed` + optional `notes` are
* writable — `confirmedAt` / `confirmedBy` are stamped server-side from
@@ -36,3 +45,4 @@ export const setInterestQualificationSchema = z.object({
export type CreateQualificationCriterionInput = z.infer<typeof createQualificationCriterionSchema>;
export type UpdateQualificationCriterionInput = z.infer<typeof updateQualificationCriterionSchema>;
export type SetInterestQualificationInput = z.infer<typeof setInterestQualificationSchema>;
export type ReorderQualificationCriteriaInput = z.infer<typeof reorderQualificationCriteriaSchema>;