@7365admin1/layer-common 3.1.4-staging.58 → 3.2.1-staging.59

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5754751: 08072026 Version Release
8
+
3
9
  ## 3.1.3
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,376 @@
1
+ <template>
2
+ <v-card class="pa-2">
3
+ <!-- Header -->
4
+ <v-card-title class="d-flex align-center justify-space-between pb-2">
5
+ <span class="text-h6 font-weight-bold">
6
+ {{ isEditing ? "Edit Plan" : "Create New Plan" }}
7
+ </span>
8
+
9
+ <v-btn
10
+ icon="mdi-close"
11
+ variant="text"
12
+ density="comfortable"
13
+ aria-label="Close"
14
+ @click="$emit('cancel')"
15
+ />
16
+ </v-card-title>
17
+
18
+ <v-divider />
19
+
20
+ <!-- Form Content -->
21
+ <v-form ref="formRef" class="my-4" @submit.prevent="submit">
22
+ <v-card-text class="pa-2 max-h-70vh overflow-y-auto">
23
+ <!-- Plan Details Section -->
24
+ <div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
25
+ Plan Details
26
+ </div>
27
+
28
+ <!-- Plan Name -->
29
+ <v-text-field
30
+ v-model="form.name"
31
+ label="Plan Name"
32
+ placeholder="e.g., Business"
33
+ variant="outlined"
34
+ density="compact"
35
+ :error-messages="errors.name"
36
+ class="mb-3"
37
+ required
38
+ >
39
+ <template #label>
40
+ Plan Name <span class="text-error">*</span>
41
+ </template>
42
+ </v-text-field>
43
+
44
+ <!-- Description -->
45
+ <v-textarea
46
+ v-model="form.description"
47
+ label="Description (optional)"
48
+ placeholder="Enter description"
49
+ variant="outlined"
50
+ density="compact"
51
+ rows="2"
52
+ auto-grow
53
+ class="mb-3"
54
+ />
55
+
56
+ <!-- Max Seats -->
57
+ <v-text-field
58
+ v-model.number="form.maxSeats"
59
+ type="number"
60
+ min="0"
61
+ variant="outlined"
62
+ density="compact"
63
+ class="mb-4"
64
+ required
65
+ >
66
+ <template #label>
67
+ Max seats <span class="text-error">*</span>
68
+ </template>
69
+ </v-text-field>
70
+
71
+ <v-divider class="my-4" />
72
+
73
+ <!-- Pricing Section -->
74
+ <div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
75
+ Pricing
76
+ </div>
77
+
78
+ <!-- Plan Type -->
79
+ <v-select
80
+ v-model="form.planType"
81
+ :items="planTypeOptions"
82
+ variant="outlined"
83
+ density="compact"
84
+ class="mb-3"
85
+ >
86
+ <template #label>
87
+ Subscription plan type <span class="text-error">*</span>
88
+ </template>
89
+ </v-select>
90
+
91
+ <!-- Billing Cycle -->
92
+ <v-select
93
+ v-if="form.planType !== 'free_bundle'"
94
+ v-model="form.billingCycle"
95
+ :items="billingCycleOptions"
96
+ placeholder="Select billing cycle"
97
+ variant="outlined"
98
+ density="compact"
99
+ class="mb-3"
100
+ >
101
+ <template #label>
102
+ Billing cycle <span class="text-error">*</span>
103
+ </template>
104
+ </v-select>
105
+
106
+ <!-- Bundle Applications (Chip Toggle) -->
107
+ <div v-if="form.planType !== 'custom_apps'" class="mb-4">
108
+ <div class="text-caption font-weight-medium text-grey-darken-2 mb-2">
109
+ Included Applications
110
+ </div>
111
+
112
+ <v-chip-group
113
+ v-model="form.bundleApps"
114
+ column
115
+ multiple
116
+ selected-class="bg-grey-darken-3 text-white"
117
+ >
118
+ <v-chip
119
+ v-for="app in AVAILABLE_APPLICATIONS"
120
+ :key="app.id"
121
+ :value="app.id"
122
+ filter
123
+ variant="outlined"
124
+ size="small"
125
+ >
126
+ {{ app.name }}
127
+ </v-chip>
128
+ </v-chip-group>
129
+ </div>
130
+
131
+ <!-- Bundle Price -->
132
+ <v-text-field
133
+ v-if="form.planType === 'paid_bundle'"
134
+ v-model.number="form.bundlePrice"
135
+ type="number"
136
+ min="0"
137
+ placeholder="0"
138
+ prefix="$"
139
+ :suffix="form.billingCycle === 'annually' ? '/yr' : '/mo'"
140
+ variant="outlined"
141
+ density="compact"
142
+ class="mb-3"
143
+ >
144
+ <template #label>
145
+ Price <span class="text-error">*</span>
146
+ </template>
147
+ </v-text-field>
148
+
149
+ <!-- Custom Applications (List Checkbox) -->
150
+ <div v-if="form.planType === 'custom_apps'" class="mb-4">
151
+ <div class="text-caption font-weight-medium text-grey-darken-2 mb-1">
152
+ Included Applications <span class="text-error">*</span>
153
+ </div>
154
+
155
+ <v-card variant="outlined" class="pa-1 mb-3">
156
+ <v-list density="compact" class="py-0">
157
+ <v-list-item
158
+ v-for="app in AVAILABLE_APPLICATIONS"
159
+ :key="app.id"
160
+ class="px-2"
161
+ >
162
+ <template #prepend>
163
+ <v-checkbox-btn
164
+ :model-value="form.customApps.includes(app.id)"
165
+ density="compact"
166
+ @update:model-value="toggleApp(form.customApps, app.id)"
167
+ />
168
+ </template>
169
+
170
+ <v-list-item-title class="text-body-2">
171
+ {{ app.name }}
172
+ </v-list-item-title>
173
+
174
+ <template #append>
175
+ <span class="text-caption text-grey">
176
+ ${{ form.billingCycle === 'annually' ? app.yearlyPrice : app.monthlyPrice }}/{{ form.billingCycle === 'annually' ? 'yr' : 'mo' }}
177
+ </span>
178
+ </template>
179
+ </v-list-item>
180
+ </v-list>
181
+ </v-card>
182
+
183
+ <!-- Custom Total Price Display -->
184
+ <v-sheet
185
+ color="grey-lighten-4"
186
+ rounded
187
+ class="pa-3 d-flex align-center justify-space-between mb-1"
188
+ >
189
+ <span class="text-caption text-grey-darken-2">
190
+ Total price {{ form.billingCycle === 'annually' ? '/ year' : '/ month' }}
191
+ </span>
192
+ <span class="text-subtitle-1 font-weight-bold">
193
+ ${{ customTotalPrice.toLocaleString() }}
194
+ </span>
195
+ </v-sheet>
196
+
197
+ <p class="text-caption text-grey">
198
+ Calculated automatically from the selected applications.
199
+ </p>
200
+ </div>
201
+
202
+ <!-- Global Pricing Error Message -->
203
+ <v-alert
204
+ v-if="errors.pricing"
205
+ type="error"
206
+ variant="tonal"
207
+ density="compact"
208
+ class="mt-2 text-caption"
209
+ >
210
+ {{ errors.pricing }}
211
+ </v-alert>
212
+ </v-card-text>
213
+
214
+ <v-divider />
215
+
216
+ <!-- Footer Buttons -->
217
+ <v-card-actions class="pt-3 px-2">
218
+ <v-btn
219
+ variant="outlined"
220
+ color="grey-darken-1"
221
+ class="flex-1 text-none"
222
+ @click="$emit('cancel')"
223
+ >
224
+ Cancel
225
+ </v-btn>
226
+
227
+ <v-btn
228
+ type="submit"
229
+ color="grey-darken-4"
230
+ variant="flat"
231
+ class="flex-1 text-none"
232
+ >
233
+ {{ isEditing ? "Save Changes" : "Create Plan" }}
234
+ </v-btn>
235
+ </v-card-actions>
236
+ </v-form>
237
+ </v-card>
238
+ </template>
239
+
240
+ <script setup lang="ts">
241
+ import { reactive, computed, watch } from "vue";
242
+
243
+ import {
244
+ AVAILABLE_APPLICATIONS,
245
+ type PlanFormState,
246
+ type SubscriptionPlan,
247
+ } from "../types/subscription";
248
+
249
+ const props = defineProps<{
250
+ initialPlan?: SubscriptionPlan | null;
251
+ }>();
252
+
253
+ const emit = defineEmits<{
254
+ (e: "cancel"): void;
255
+ (e: "submit", payload: PlanFormState): void;
256
+ }>();
257
+
258
+ // Select options
259
+ const planTypeOptions = [
260
+ { title: "Free (Bundle)", value: "free_bundle" },
261
+ { title: "Paid (Bundle)", value: "paid_bundle" },
262
+ { title: "Paid (Custom Apps)", value: "custom_apps" },
263
+ ];
264
+
265
+ const billingCycleOptions = [
266
+ { title: "Monthly", value: "monthly" },
267
+ { title: "Annually", value: "annually" },
268
+ ];
269
+
270
+ const isEditing = computed(() => !!props.initialPlan);
271
+
272
+ const form = reactive<PlanFormState>({
273
+ name: props.initialPlan?.name ?? "",
274
+ description: props.initialPlan?.description ?? "",
275
+ maxSeats: props.initialPlan?.maxSeats ?? 0,
276
+ planType: props.initialPlan?.planType ?? "free_bundle",
277
+ billingCycle: props.initialPlan?.billingCycle ?? null,
278
+ bundleApps:
279
+ props.initialPlan?.planType !== "custom_apps"
280
+ ? [...(props.initialPlan?.applicationIds ?? [])]
281
+ : [],
282
+ bundlePrice: props.initialPlan?.price ?? null,
283
+ customApps:
284
+ props.initialPlan?.planType === "custom_apps"
285
+ ? [...(props.initialPlan?.applicationIds ?? [])]
286
+ : [],
287
+ });
288
+
289
+ const errors = reactive({
290
+ name: "",
291
+ pricing: "",
292
+ });
293
+
294
+ /*
295
+ * Reset pricing fields when plan type changes
296
+ */
297
+ watch(
298
+ () => form.planType,
299
+ (type) => {
300
+ if (type === "free_bundle") {
301
+ form.billingCycle = null;
302
+ form.bundlePrice = null;
303
+ }
304
+ }
305
+ );
306
+
307
+ /*
308
+ * Toggle application selection helper
309
+ */
310
+ function toggleApp(list: string[], id: string) {
311
+ const index = list.indexOf(id);
312
+ if (index === -1) {
313
+ list.push(id);
314
+ } else {
315
+ list.splice(index, 1);
316
+ }
317
+ }
318
+
319
+ /*
320
+ * Custom app total price calculation
321
+ */
322
+ const customTotalPrice = computed(() => {
323
+ return form.customApps.reduce((total, id) => {
324
+ const app = AVAILABLE_APPLICATIONS.find((item) => item.id === id);
325
+ if (!app) return total;
326
+
327
+ return (
328
+ total +
329
+ (form.billingCycle === "annually" ? app.yearlyPrice : app.monthlyPrice)
330
+ );
331
+ }, 0);
332
+ });
333
+
334
+ /*
335
+ * Validate Form
336
+ */
337
+ function validate(): boolean {
338
+ errors.name = form.name.trim() ? "" : "Plan name is required.";
339
+ errors.pricing = "";
340
+
341
+ if (form.planType !== "free_bundle" && !form.billingCycle) {
342
+ errors.pricing = "Please select a billing cycle.";
343
+ } else if (
344
+ form.planType === "paid_bundle" &&
345
+ (!form.bundlePrice || form.bundlePrice <= 0)
346
+ ) {
347
+ errors.pricing = "Please enter a price.";
348
+ } else if (
349
+ form.planType === "custom_apps" &&
350
+ form.customApps.length === 0
351
+ ) {
352
+ errors.pricing = "Select at least one application.";
353
+ }
354
+
355
+ return !errors.name && !errors.pricing;
356
+ }
357
+
358
+ /*
359
+ * Submit Form
360
+ */
361
+ function submit() {
362
+ if (!validate()) {
363
+ return;
364
+ }
365
+
366
+ emit("submit", {
367
+ ...form,
368
+ });
369
+ }
370
+ </script>
371
+
372
+ <style scoped>
373
+ .max-h-70vh {
374
+ max-height: 70vh;
375
+ }
376
+ </style>
@@ -1551,12 +1551,13 @@ const moduleWidgetPermissions: Record<string, string[]> = {
1551
1551
  openWorkOrder: ["work_orders:see-all-work-orders"],
1552
1552
  workOrders: ["work_orders:see-all-work-orders"],
1553
1553
  activity: ["work_orders:see-all-work-orders"],
1554
- recentActivity: [
1555
- "work_orders:see-all-work-orders",
1556
- "work_order:see-all-work-orders",
1557
- "work-order:see-all-work-orders",
1558
- "work-orders:see-all-work-orders",
1559
- "work_orders:see-all-work-orders",
1554
+ taskSchedule: [
1555
+ "cleaning-schedule-mgmt:see-all-schedules",
1556
+ "schedule-task-mgmt:see-all-schedule-tasks",
1557
+ ],
1558
+ taskCompleted: [
1559
+ "cleaning-schedule-mgmt:see-all-schedules",
1560
+ "schedule-task-mgmt:see-all-schedule-tasks",
1560
1561
  ],
1561
1562
  staffStatus: ["attendance-mgmt:see-all-attendance"],
1562
1563
  attendance: ["attendance-mgmt:see-all-attendance"],