diff --git a/apps/ops/.quasar/client-entry.js b/apps/ops/.quasar/client-entry.js index de61936..8c0244d 100644 --- a/apps/ops/.quasar/client-entry.js +++ b/apps/ops/.quasar/client-entry.js @@ -33,6 +33,8 @@ import 'quasar/dist/quasar.css' import 'src/css/app.scss' +import 'src/css/tech.scss' + import createQuasarApp from './app.js' import quasarUserOptions from './quasar-user-options.js' diff --git a/apps/ops/src/composables/useClientData.js b/apps/ops/src/composables/useClientData.js index da8f017..cc09770 100644 --- a/apps/ops/src/composables/useClientData.js +++ b/apps/ops/src/composables/useClientData.js @@ -70,8 +70,11 @@ export function useClientData (deps) { fields: ['name', 'plan_name', 'service_category', 'monthly_price', 'billing_cycle', 'service_location', 'status', 'start_date', 'end_date', 'cancellation_date', 'contract_duration', 'product_sku', 'speed_down', 'speed_up', - 'radius_user', 'device'], - limit: 100, orderBy: 'start_date desc, creation desc', + 'radius_user', 'device', 'display_order'], + limit: 100, + // display_order first (dispatcher-controlled), then by start date so + // newer subs float up when display_order hasn't been set yet (= 0). + orderBy: 'display_order asc, start_date desc, creation desc', }) // Map Service Subscription → UI row shape (matches what useSubscriptionGroups @@ -106,6 +109,7 @@ export function useClientData (deps) { radius_user: doc.radius_user || '', radius_pwd: '', device: doc.device || '', + display_order: Number(doc.display_order || 0), qty: 1, })) } diff --git a/apps/ops/src/composables/useSubscriptionActions.js b/apps/ops/src/composables/useSubscriptionActions.js index c153772..a18638d 100644 --- a/apps/ops/src/composables/useSubscriptionActions.js +++ b/apps/ops/src/composables/useSubscriptionActions.js @@ -188,8 +188,35 @@ export function useSubscriptionActions (subscriptions, customer, comments, inval } catch {} } - function onSubDragChange (evt, locName) { - if (evt.added || evt.removed) invalidateCache(locName) + // vuedraggable mutates the bound list in-place, so by the time this + // handler fires the `items` array (passed by the template) already + // reflects the new order. We persist display_order in 10-step increments + // — leaves room for manual inserts (frappe Link-sort-by-number ugh) — + // and only PUT the rows whose position actually changed to avoid + // N writes on every drag. + async function onSubDragChange (evt, locName, items = null) { + // vuedraggable emits an object with {added|removed|moved}; old signature + // (evt, locName) with no items still works for cache-only invalidation. + if (evt.added || evt.removed || evt.moved) invalidateCache(locName) + if (!items || !items.length) return + + const writes = [] + for (let i = 0; i < items.length; i++) { + const want = (i + 1) * 10 // 10, 20, 30… (keeps manual gaps) + const item = items[i] + if (!item || item.display_order === want) continue + item.display_order = want // optimistic local update + writes.push( + authFetch(BASE_URL + '/api/resource/Service%20Subscription/' + encodeURIComponent(item.name), { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ display_order: want }), + }).catch(() => null) + ) + } + if (writes.length) { + try { await Promise.all(writes) } catch {} + } } return { diff --git a/apps/ops/src/pages/ClientDetailPage.vue b/apps/ops/src/pages/ClientDetailPage.vue index ef21f61..54f23d3 100644 --- a/apps/ops/src/pages/ClientDetailPage.vue +++ b/apps/ops/src/pages/ClientDetailPage.vue @@ -202,7 +202,7 @@
+ @change="onSubDragChange($event, loc.name, section.items)">