@7365admin1/layer-common 3.2.8-staging.202 → 3.2.8-staging.204

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,177 +70,64 @@
70
70
  </section>
71
71
 
72
72
  <!-- ===== SUBSCRIPTION INFORMATION ===== -->
73
+ <!--
74
+ READ-ONLY on purpose. This section used to offer six dropdowns and two
75
+ date boxes that looked editable and saved nothing: the plan list was two
76
+ invented names, and Start / End were always blank because nothing ever
77
+ loaded them. It now shows what the client's record actually says, using
78
+ the same wording as the Client List. Setting or changing a subscription is
79
+ a separate piece of work with a real endpoint behind it.
80
+ -->
73
81
  <section class="section">
74
- <h3>Subscription Information</h3>
75
-
76
- <div class="row">
77
- <div class="field">
78
- <label>Subscription plan type</label>
79
- <div class="select-wrap">
80
- <select v-model="form.planType">
81
- <option v-for="opt in PLAN_TYPE_OPTIONS" :key="opt" :value="opt">
82
- {{ opt }}
83
- </option>
84
- </select>
85
- <svg class="chevron" viewBox="0 0 10 6" fill="none">
86
- <path
87
- d="M1 1l4 4 4-4"
88
- stroke="currentColor"
89
- stroke-width="1.4"
90
- stroke-linecap="round"
91
- />
92
- </svg>
93
- </div>
94
- </div>
95
- <div class="field">
96
- <label>Auto Renewal</label>
97
- <div class="select-wrap">
98
- <select v-model="form.autoRenewal">
99
- <option
100
- v-for="opt in AUTO_RENEWAL_OPTIONS"
101
- :key="opt"
102
- :value="opt"
103
- >
104
- {{ opt }}
105
- </option>
106
- </select>
107
- <svg class="chevron" viewBox="0 0 10 6" fill="none">
108
- <path
109
- d="M1 1l4 4 4-4"
110
- stroke="currentColor"
111
- stroke-width="1.4"
112
- stroke-linecap="round"
113
- />
114
- </svg>
115
- </div>
116
- </div>
82
+ <div class="section-head">
83
+ <h3>Subscription Information</h3>
84
+ <!--
85
+ The values stay read-only - they are what the client's record says.
86
+ Changing them is a real form with real rules behind it, so this opens
87
+ that rather than pretending a dropdown here would save anything.
88
+ -->
89
+ <button class="btn-manage-sub" @click="$emit('manage-subscription')">
90
+ {{ subscription.state === 'none' ? 'Set up subscription' : 'Edit subscription' }}
91
+ </button>
117
92
  </div>
118
93
 
119
- <div class="row">
120
- <div class="field">
121
- <label>Billing Cycle</label>
122
- <div class="select-wrap">
123
- <select v-model="form.billingCycle" @change="onBillingCycleChange">
124
- <option
125
- v-for="opt in BILLING_CYCLE_OPTIONS"
126
- :key="opt"
127
- :value="opt"
128
- >
129
- {{ opt }}
130
- </option>
131
- </select>
132
- <svg class="chevron" viewBox="0 0 10 6" fill="none">
133
- <path
134
- d="M1 1l4 4 4-4"
135
- stroke="currentColor"
136
- stroke-width="1.4"
137
- stroke-linecap="round"
138
- />
139
- </svg>
94
+ <p v-if="subscription.state === 'none'" class="sub-none">
95
+ No subscription set up for this client yet.
96
+ </p>
97
+
98
+ <template v-else>
99
+ <div class="row">
100
+ <div class="field">
101
+ <label>Subscription</label>
102
+ <p
103
+ class="read-value"
104
+ :class="{ 'needs-attention': subscription.needsAttention }"
105
+ >
106
+ {{ subscription.label }}
107
+ </p>
140
108
  </div>
141
- </div>
142
- <div class="field">
143
- <label>Status</label>
144
- <div class="select-wrap">
145
- <select v-model="form.status">
146
- <option v-for="opt in STATUS_OPTIONS" :key="opt" :value="opt">
147
- {{ opt }}
148
- </option>
149
- </select>
150
- <svg class="chevron" viewBox="0 0 10 6" fill="none">
151
- <path
152
- d="M1 1l4 4 4-4"
153
- stroke="currentColor"
154
- stroke-width="1.4"
155
- stroke-linecap="round"
156
- />
157
- </svg>
109
+ <div class="field">
110
+ <label>Billing Cycle</label>
111
+ <p class="read-value">{{ subscription.billingCycle }}</p>
158
112
  </div>
159
113
  </div>
160
- </div>
161
114
 
162
- <div class="row row-single">
163
- <div class="field">
164
- <label>Subscription plan</label>
165
- <div class="select-wrap">
166
- <select v-model="form.subscriptionPlan">
167
- <option
168
- v-for="opt in subscriptionPlanOptions"
169
- :key="opt"
170
- :value="opt"
171
- >
172
- {{ opt }}
173
- </option>
174
- </select>
175
- <svg class="chevron" viewBox="0 0 10 6" fill="none">
176
- <path
177
- d="M1 1l4 4 4-4"
178
- stroke="currentColor"
179
- stroke-width="1.4"
180
- stroke-linecap="round"
181
- />
182
- </svg>
115
+ <div class="row">
116
+ <div class="field">
117
+ <label>Start Date</label>
118
+ <p class="read-value">{{ subscription.start }}</p>
183
119
  </div>
184
- <p class="hint">
185
- Values of dropdown are determined by the list under selected billing
186
- cycle.
187
- </p>
188
- </div>
189
- </div>
190
-
191
- <div class="row">
192
- <div class="field">
193
- <label>Start Date</label>
194
- <div class="date-input">
195
- <input type="text" :value="form.startDate" disabled />
196
- <svg viewBox="0 0 16 16" fill="none" width="14" height="14">
197
- <rect
198
- x="1"
199
- y="3"
200
- width="14"
201
- height="12"
202
- rx="2"
203
- stroke="currentColor"
204
- stroke-width="1.3"
205
- />
206
- <path d="M1 7h14" stroke="currentColor" stroke-width="1.3" />
207
- <path
208
- d="M5 1v3M11 1v3"
209
- stroke="currentColor"
210
- stroke-width="1.3"
211
- stroke-linecap="round"
212
- />
213
- </svg>
120
+ <div class="field">
121
+ <label>End Date</label>
122
+ <p
123
+ class="read-value"
124
+ :class="{ 'needs-attention': subscription.needsAttention }"
125
+ >
126
+ {{ subscription.end }}
127
+ </p>
214
128
  </div>
215
129
  </div>
216
- <div class="field">
217
- <label>End Date</label>
218
- <div class="date-input">
219
- <input type="text" :value="form.endDate" disabled />
220
- <svg viewBox="0 0 16 16" fill="none" width="14" height="14">
221
- <rect
222
- x="1"
223
- y="3"
224
- width="14"
225
- height="12"
226
- rx="2"
227
- stroke="currentColor"
228
- stroke-width="1.3"
229
- />
230
- <path d="M1 7h14" stroke="currentColor" stroke-width="1.3" />
231
- <path
232
- d="M5 1v3M11 1v3"
233
- stroke="currentColor"
234
- stroke-width="1.3"
235
- stroke-linecap="round"
236
- />
237
- </svg>
238
- </div>
239
- </div>
240
- </div>
241
- <p class="hint hint-below">
242
- Start and end date depend on the billing cycle, and cannot be edited.
243
- </p>
130
+ </template>
244
131
  </section>
245
132
 
246
133
  <!-- ===== INCLUDED APPLICATION ===== -->
@@ -281,6 +168,7 @@
281
168
  import { reactive, computed, watch } from 'vue'
282
169
  import useOrg from '../composables/useOrg'
283
170
  import useCustomerSite from '../composables/useCustomerSite'
171
+ import { describeClientSubscription } from '../utils/client-subscription'
284
172
  import useUser from '../composables/useUser'
285
173
 
286
174
  const {
@@ -356,24 +244,10 @@ const props = defineProps<{
356
244
  const emit = defineEmits<{
357
245
  (e: 'cancel'): void
358
246
  (e: 'submit', payload: Record<string, any>): void
247
+ (e: 'manage-subscription'): void
359
248
  }>()
360
249
 
361
250
  /* ── CONSTANTS ── */
362
- const PLAN_TYPE_OPTIONS = [
363
- 'Free (Bundle)',
364
- 'Paid (Bundle)',
365
- 'Paid (Custom App)'
366
- ]
367
- const AUTO_RENEWAL_OPTIONS = ['No', 'Yes']
368
- const BILLING_CYCLE_OPTIONS = ['Monthly', 'Annually']
369
- const STATUS_OPTIONS = ['Active', 'Suspend']
370
-
371
- // Subscription plan options keyed by billing cycle (annotation from mockup)
372
- const SUBSCRIPTION_PLAN_BY_CYCLE: Record<string, string[]> = {
373
- Monthly: ['Standard Plan', 'Premium Plan'],
374
- Annually: ['Standard Plan', 'Premium Plan']
375
- }
376
-
377
251
  const PHONE_RULES: Record<string, number> = {
378
252
  '+60': 9,
379
253
  '+61': 9,
@@ -399,31 +273,15 @@ const form = reactive({
399
273
  organizationName: '',
400
274
  phone: '',
401
275
 
402
- planType: 'Paid (Bundle)',
403
- autoRenewal: 'Yes',
404
- billingCycle: 'Monthly',
405
- status: 'Active',
406
- subscriptionPlan: 'Standard Plan',
407
-
408
- startDate: '',
409
- endDate: '',
410
-
411
276
  includedApps: [] as string[],
412
277
 
413
278
  sites: [] as SiteRow[]
414
279
  })
415
280
 
416
281
  /* ── COMPUTED ── */
417
- const subscriptionPlanOptions = computed(
418
- () => SUBSCRIPTION_PLAN_BY_CYCLE[form.billingCycle] ?? []
419
- )
420
-
421
- /* ── WATCH: keep subscription plan valid when billing cycle changes ── */
422
- watch(subscriptionPlanOptions, options => {
423
- if (!options.includes(form.subscriptionPlan)) {
424
- form.subscriptionPlan = options[0] ?? ''
425
- }
426
- })
282
+ // The row the Client List already fetched carries the subscription sub-document,
283
+ // so the drawer says exactly what the list says - no second, disagreeing source.
284
+ const subscription = computed(() => describeClientSubscription(props.client))
427
285
 
428
286
  watch(
429
287
  () => form.name,
@@ -454,10 +312,6 @@ watch([phonePrefix, phoneNumber], ([prefix, value]) => {
454
312
  })
455
313
 
456
314
  /* ── EVENTS ── */
457
- function onBillingCycleChange() {
458
- // Start / End date are derived from billing cycle and are not editable.
459
- // Recompute or refetch here as needed once wired to a real API.
460
- }
461
315
  function validateForm() {
462
316
  errors.name = ''
463
317
  errors.organizationName = ''
@@ -647,6 +501,29 @@ watch(
647
501
  .section {
648
502
  margin-bottom: 26px;
649
503
  }
504
+ .section-head {
505
+ display: flex;
506
+ align-items: center;
507
+ justify-content: space-between;
508
+ gap: 12px;
509
+ flex-wrap: wrap;
510
+ }
511
+ .section-head h3 {
512
+ margin-bottom: 0;
513
+ }
514
+ .btn-manage-sub {
515
+ padding: 6px 14px;
516
+ font-size: 12px;
517
+ font-weight: 600;
518
+ border: 1px solid var(--border);
519
+ border-radius: var(--r-pill);
520
+ background: var(--card);
521
+ color: var(--accent-text);
522
+ cursor: pointer;
523
+ white-space: nowrap;
524
+ margin-bottom: 14px;
525
+ }
526
+ .btn-manage-sub:hover { background: var(--hover); }
650
527
  .section h3 {
651
528
  font-size: 15px;
652
529
  font-weight: 700;
@@ -660,10 +537,6 @@ watch(
660
537
  gap: 20px;
661
538
  margin-bottom: 14px;
662
539
  }
663
- .row-single {
664
- grid-template-columns: 1fr;
665
- }
666
-
667
540
  .field {
668
541
  display: flex;
669
542
  flex-direction: column;
@@ -697,68 +570,17 @@ watch(
697
570
  color: var(--text2);
698
571
  }
699
572
 
700
- /* ===== SELECT ===== */
701
- .select-wrap {
702
- position: relative;
703
- }
704
- .select-wrap select {
705
- width: 100%;
706
- height: 38px;
707
- border: 1px solid var(--border);
708
- border-radius: var(--r-inner);
709
- padding: 0 30px 0 12px;
710
- font-size: 13px;
573
+ /* ===== READ-ONLY VALUES ===== */
574
+ /* A value the staff member reads rather than edits. */
575
+ .read-value {
576
+ font-size: 14px;
711
577
  color: var(--text);
712
- background: var(--card);
713
- appearance: none;
714
- cursor: pointer;
715
- outline: none;
716
- }
717
- .select-wrap select:focus {
718
- border-color: var(--muted);
719
- }
720
- .select-wrap .chevron {
721
- position: absolute;
722
- right: 11px;
723
- top: 50%;
724
- transform: translateY(-50%);
725
- width: 10px;
726
- height: 6px;
727
- pointer-events: none;
728
- }
729
-
730
- /* ===== DATE ===== */
731
- .date-input {
732
- position: relative;
733
- display: flex;
734
- align-items: center;
735
- }
736
- .date-input input {
737
- width: 100%;
738
- height: 38px;
739
- border: 1px solid var(--border);
740
- border-radius: var(--r-inner);
741
- padding: 0 34px 0 12px;
742
- font-size: 13px;
743
- color: var(--text2);
744
- background: var(--bg);
745
- outline: none;
746
- }
747
- .date-input svg {
748
- position: absolute;
749
- right: 12px;
750
- pointer-events: none;
751
- }
752
-
753
- /* ===== HINT ===== */
754
- .hint {
755
- font-size: 11px;
756
- color: var(--text2);
757
- line-height: 1.4;
758
- }
759
- .hint-below {
760
- margin-top: 4px;
578
+ padding: 10px 0;
579
+ min-height: 20px;
761
580
  }
581
+ .sub-none { font-size: 14px; color: var(--text2); }
582
+ /* Owner decision 8: an end date has passed and somebody has to look at it. */
583
+ .needs-attention { color: var(--warn); font-weight: 500; }
762
584
 
763
585
  /* ===== INCLUDED APPS ===== */
764
586
  .app-list {