@7365admin1/layer-common 3.1.3 → 3.1.4-staging.47

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.
@@ -34,3 +34,14 @@ jobs:
34
34
 
35
35
  - name: Publish to npm with staging tag
36
36
  run: npm publish --tag staging --registry https://registry.npmjs.org
37
+
38
+ - name: Notify Discord on failure
39
+ if: failure()
40
+ env:
41
+ DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
42
+ run: |
43
+ MESSAGE="@everyone :rotating_light: **Publish Staging** failed in \`${{ github.repository }}\` (\`${{ github.workflow }}\`)
44
+ ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
45
+ curl -s -X POST -H "Content-Type: application/json" \
46
+ -d "$(jq -n --arg content "$MESSAGE" '{content: $content}')" \
47
+ "$DISCORD_WEBHOOK_URL"
@@ -48,7 +48,7 @@
48
48
  <v-list-item class="cursor-pointer" @click="openMediaViewer(item._id, item.mimeType)">
49
49
  <template #prepend>
50
50
  <v-avatar size="36" rounded="sm" class="mr-3">
51
- <v-img :src="getFileUrl(item._id)" cover>
51
+ <v-img :src="imageCover(item._id)" cover>
52
52
  <template #error>
53
53
  <v-icon color="green" size="20">mdi-image</v-icon>
54
54
  </template>
@@ -97,7 +97,7 @@
97
97
  </v-row>
98
98
 
99
99
  <!-- Media Carousel (images + videos) -->
100
- <ImageCarousel v-model="showCarousel" :active-file-id="activeFileId" :files="carouselFiles" />
100
+ <ImageCarousel v-model="showCarousel" :active-file-id="activeFileId" :files="carouselFiles" :anpr="anpr" :site="site" />
101
101
 
102
102
  <!-- Document Viewer -->
103
103
  <DocumentViewer v-model="showDocumentViewer" :file-id="activeFileId" :file-name="activeFileName"
@@ -123,10 +123,18 @@ const props = defineProps({
123
123
  showFileTypeTitle: {
124
124
  type: Boolean,
125
125
  default: false,
126
+ },
127
+ anpr: {
128
+ type: Boolean,
129
+ default: false
130
+ },
131
+ site: {
132
+ type: String,
133
+ default: ""
126
134
  }
127
135
  });
128
136
 
129
- const { getFileUrl, urlToFile, getFileById } = useFile();
137
+ const { getFileUrl, urlToFile, getFileById, getFileUrlAnpr } = useFile();
130
138
 
131
139
  const rawFileArray = ref<{ file: File, _id: string, preview: boolean, mimeType: string }[]>([]);
132
140
  const loading = ref(true);
@@ -159,6 +167,12 @@ function openDocumentViewer(id: string, name: string, mimeType: string) {
159
167
  showDocumentViewer.value = true;
160
168
  }
161
169
 
170
+ const imageCover = (id: string) => {
171
+ if(props.anpr && props.site){
172
+ return getFileUrlAnpr(`${props.site}/${id}`)
173
+ } else return getFileUrl(id)
174
+ }
175
+
162
176
  function getFileIcon(mimeType: string): string {
163
177
  if (mimeType.startsWith('image/')) return 'mdi-image';
164
178
  if (mimeType.startsWith('video/')) return 'mdi-video';
@@ -206,7 +220,11 @@ watchEffect(async () => {
206
220
 
207
221
  try {
208
222
  // Try to get the actual file with proper MIME type detection from blob
209
- const fileUrl = getFileUrl(item.id);
223
+ // const fileUrl = getFileUrl(item.id);
224
+ let fileUrl = ""
225
+ if(props.anpr && props.site){
226
+ fileUrl = getFileUrlAnpr(`${props.site}/${item.id}`)
227
+ } else fileUrl = getFileUrl(item.id);
210
228
  file = await urlToFile(fileUrl, item.name);
211
229
  mimeType = file.type || 'application/octet-stream';
212
230
 
@@ -984,60 +984,44 @@
984
984
  <div
985
985
  v-for="item in formattedActivePatrolItems"
986
986
  :key="item.id || item._id"
987
- class="d-flex align-center justify-space-between"
988
- style="
989
- padding: 16px 20px;
990
- border: 1px solid #e2e8f0;
991
- border-radius: 12px;
992
- margin-bottom: 12px;
993
- background-color: #ffffff;
994
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
995
- "
987
+ class="active-patrol-row"
996
988
  >
997
- <div style="flex: 1; min-width: 140px">
998
- <strong
999
- style="
1000
- display: block;
1001
- font-size: 15px;
1002
- font-weight: 700;
1003
- color: #0b2f47;
1004
- margin-bottom: 4px;
1005
- "
1006
- >{{ item.title || item.name }}</strong
1007
- >
1008
- <span style="font-size: 13px; color: #94a3b8">{{
1009
- item.subtitle || item.location || ""
1010
- }}</span>
989
+ <div class="active-patrol-main">
990
+ <strong>{{ item.title || item.name }}</strong>
991
+ <span>{{ item.subtitle || item.location || "" }}</span>
1011
992
  </div>
1012
993
 
1013
- <div
1014
- style="flex: 1; display: flex; align-items: center; gap: 12px"
1015
- >
1016
- <AvatarMain
1017
- :name="item.person || item.assignedTo"
1018
- :size="36"
1019
- />
1020
- <span style="font-size: 14px; color: #64748b">{{
1021
- item.person || item.assignedTo
1022
- }}</span>
994
+ <div class="active-patrol-assignees">
995
+ <div class="active-patrol-assignee">
996
+ <AvatarMain :name="item.primaryAssignee" :size="24" />
997
+ <span>{{ item.primaryAssignee }}</span>
998
+ </div>
999
+ <span
1000
+ v-if="item.extraAssigneeCount > 0"
1001
+ class="active-patrol-more-pill active-patrol-tooltip"
1002
+ :data-tooltip="item.extraAssignees.join(', ')"
1003
+ >
1004
+ +{{ item.extraAssigneeCount }}
1005
+ </span>
1023
1006
  </div>
1024
1007
 
1025
- <div style="flex: 1; display: flex; justify-content: center">
1008
+ <div class="active-patrol-statuses">
1026
1009
  <div
1027
- :style="getPatrolStatusStyle(item.status)"
1028
- style="
1029
- padding: 6px 16px;
1030
- border-radius: 20px;
1031
- font-size: 13px;
1032
- font-weight: 500;
1033
- text-transform: capitalize;
1034
- "
1010
+ class="active-patrol-status-pill"
1011
+ :style="getPatrolStatusStyle(item.primaryStatus)"
1035
1012
  >
1036
- {{ item.status }}
1013
+ {{ item.primaryStatus }}
1037
1014
  </div>
1015
+ <span
1016
+ v-if="item.extraStatusCount > 0"
1017
+ class="active-patrol-more-pill active-patrol-tooltip"
1018
+ :data-tooltip="item.extraStatuses.join(', ')"
1019
+ >
1020
+ +{{ item.extraStatusCount }}
1021
+ </span>
1038
1022
  </div>
1039
1023
 
1040
- <div style="flex: 0 0 auto; text-align: right">
1024
+ <div class="active-patrol-action">
1041
1025
  <v-btn
1042
1026
  variant="outlined"
1043
1027
  class="text-none font-weight-medium"
@@ -1545,30 +1529,16 @@ const { userAppRole } = useLocalSetup();
1545
1529
  const { downloadPDF } = usePDFDownload();
1546
1530
  const route = useRoute();
1547
1531
 
1548
- const dashboardWidgetPermissions: Record<string, string> = {
1549
- activity: "dashboard:view-activity",
1550
- recentActivity: "dashboard:view-activity",
1551
- taskSchedule: "dashboard:view-task-schedule",
1552
- staffStatus: "dashboard:view-attendance-widget",
1553
- attendance: "dashboard:view-attendance-widget",
1554
- feedbacks: "dashboard:view-feedbacks-widget",
1555
- workOrderStatus: "dashboard:view-work-order-status",
1556
- activePatrol: "dashboard:view-active-patrol",
1557
- upcomingEvents: "dashboard:view-upcoming-events",
1558
- todayReminders: "dashboard:view-today-reminders",
1559
- todayAttentions: "dashboard:view-today-attentions",
1560
- };
1561
-
1562
1532
  const propertyWidgetPermissions: Record<string, string[]> = {
1563
1533
  workOrders: [
1564
- "workOrder:see-all-work-orders",
1534
+ "work_orders:see-all-work-orders",
1565
1535
  "work-order:see-all-work-orders",
1566
1536
  ],
1567
1537
  incidents: ["incident-reports:see-incident-reports"],
1568
1538
  visitors: ["visitorManagement:see-all-visitor"],
1569
1539
  facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
1570
1540
  activity: [
1571
- "workOrder:see-all-work-orders",
1541
+ "work_orders:see-all-work-orders",
1572
1542
  "work-order:see-all-work-orders",
1573
1543
  ],
1574
1544
  upcomingEvents: ["event-mgmt:see-all-event"],
@@ -1578,7 +1548,7 @@ const propertyWidgetPermissions: Record<string, string[]> = {
1578
1548
  "facility-booking-mgmt:see-all-facility-booking",
1579
1549
  ],
1580
1550
  workOrderStatus: [
1581
- "workOrder:see-all-work-orders",
1551
+ "work_orders:see-all-work-orders",
1582
1552
  "work-order:see-all-work-orders",
1583
1553
  ],
1584
1554
  feedbacks: ["feedbacks:see-all-feedback"],
@@ -1603,14 +1573,14 @@ const moduleWidgetPermissions: Record<string, string[]> = {
1603
1573
  "work_order:see-all-work-orders",
1604
1574
  "work-order:see-all-work-orders",
1605
1575
  "work-orders:see-all-work-orders",
1576
+ "work_orders:see-all-work-orders",
1606
1577
  ],
1607
- taskSchedule: [
1608
- "cleaning-schedule-mgmt:see-all-schedules",
1609
- "schedule-task-mgmt:see-all-schedule-tasks",
1610
- ],
1611
- taskCompleted: [
1612
- "cleaning-schedule-mgmt:see-all-schedules",
1613
- "schedule-task-mgmt:see-all-schedule-tasks",
1578
+ recentActivity: [
1579
+ "work_orders:see-all-work-orders",
1580
+ "work_order:see-all-work-orders",
1581
+ "work-order:see-all-work-orders",
1582
+ "work-orders:see-all-work-orders",
1583
+ "work_orders:see-all-work-orders",
1614
1584
  ],
1615
1585
  staffStatus: ["attendance-mgmt:see-all-attendance"],
1616
1586
  attendance: ["attendance-mgmt:see-all-attendance"],
@@ -1633,9 +1603,14 @@ const moduleWidgetPermissions: Record<string, string[]> = {
1633
1603
  activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1634
1604
  };
1635
1605
 
1606
+ function hasSchedulePermission(permissions: string[], action: string) {
1607
+ return permissions.some((permission) =>
1608
+ permission.endsWith(`-schedule-mgmt:${action}`)
1609
+ );
1610
+ }
1611
+
1636
1612
  function canViewWidget(key: string) {
1637
1613
  const permissions = userAppRole.value?.permissions;
1638
- const dashboardPermission = dashboardWidgetPermissions[key];
1639
1614
  const modulePermissions = isPropertyMode.value
1640
1615
  ? propertyWidgetPermissions[key]
1641
1616
  : moduleWidgetPermissions[key];
@@ -1643,20 +1618,22 @@ function canViewWidget(key: string) {
1643
1618
  if (!permissions) return true;
1644
1619
  if (permissions.includes("*")) return true;
1645
1620
 
1621
+ if (
1622
+ !isPropertyMode.value &&
1623
+ ["taskSchedule", "taskCompleted"].includes(key)
1624
+ ) {
1625
+ return hasSchedulePermission(permissions, "see-all-schedules");
1626
+ }
1627
+
1646
1628
  if (isPropertyMode.value && modulePermissions) {
1647
1629
  return modulePermissions.some((permission) =>
1648
1630
  permissions.includes(permission)
1649
1631
  );
1650
1632
  }
1651
1633
 
1652
- if (dashboardPermission || modulePermissions) {
1653
- return (
1654
- (dashboardPermission && permissions.includes(dashboardPermission)) ||
1655
- Boolean(
1656
- modulePermissions?.some((permission) =>
1657
- permissions.includes(permission)
1658
- )
1659
- )
1634
+ if (modulePermissions) {
1635
+ return modulePermissions.some((permission) =>
1636
+ permissions.includes(permission)
1660
1637
  );
1661
1638
  }
1662
1639
 
@@ -2774,18 +2751,23 @@ const activePatrolItems = computed<any[]>(
2774
2751
 
2775
2752
  const formattedActivePatrolItems = computed<any[]>(() => {
2776
2753
  return activePatrolItems.value.map((item) => {
2777
- let status = item.status;
2778
- if (Array.isArray(status)) {
2779
- status = status.join(", ");
2780
- }
2781
- let person = item.person || item.assignedTo || "Unassigned";
2782
- if (Array.isArray(person)) {
2783
- person = person.join(", ");
2784
- }
2754
+ const statuses = normalizeListValue(item.status || "Pending");
2755
+ const assignees = normalizeListValue(
2756
+ item.person || item.assignedTo || "Unassigned"
2757
+ );
2758
+
2785
2759
  return {
2786
2760
  ...item,
2787
- status,
2788
- person,
2761
+ status: statuses.join(", "),
2762
+ statuses,
2763
+ primaryStatus: statuses[0] || "Pending",
2764
+ extraStatuses: statuses.slice(1),
2765
+ extraStatusCount: Math.max(0, statuses.length - 1),
2766
+ person: assignees.join(", "),
2767
+ assignees,
2768
+ primaryAssignee: assignees[0] || "Unassigned",
2769
+ extraAssignees: assignees.slice(1),
2770
+ extraAssigneeCount: Math.max(0, assignees.length - 1),
2789
2771
  };
2790
2772
  });
2791
2773
  });
@@ -3955,6 +3937,20 @@ function normalizeStatus(value: any) {
3955
3937
  return text.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3956
3938
  }
3957
3939
 
3940
+ function normalizeListValue(value: any) {
3941
+ if (Array.isArray(value)) {
3942
+ return value
3943
+ .flatMap((entry) => normalizeListValue(entry))
3944
+ .map((entry) => String(entry).trim())
3945
+ .filter(Boolean);
3946
+ }
3947
+
3948
+ return String(value || "")
3949
+ .split(",")
3950
+ .map((entry) => entry.trim())
3951
+ .filter(Boolean);
3952
+ }
3953
+
3958
3954
  function getStatusTone(status: any) {
3959
3955
  const s = String(status || "").toLowerCase();
3960
3956
  if (
@@ -4422,6 +4418,151 @@ function formatDashboardTime(value?: string) {
4422
4418
  border: 2px solid #f9c97c;
4423
4419
  }
4424
4420
 
4421
+ .active-patrol-row {
4422
+ align-items: center;
4423
+ background-color: #ffffff;
4424
+ border: 1px solid #e2e8f0;
4425
+ border-radius: 8px;
4426
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
4427
+ display: grid;
4428
+ gap: 10px;
4429
+ grid-template-columns: 150px minmax(0, 1fr) max-content max-content;
4430
+ margin-bottom: 12px;
4431
+ min-height: 64px;
4432
+ overflow: visible;
4433
+ padding: 12px 14px;
4434
+ }
4435
+
4436
+ .active-patrol-main {
4437
+ min-width: 0;
4438
+ }
4439
+
4440
+ .active-patrol-main strong {
4441
+ color: #0b2f47;
4442
+ display: block;
4443
+ font-size: 13px;
4444
+ font-weight: 700;
4445
+ line-height: 1.25;
4446
+ margin-bottom: 3px;
4447
+ overflow-wrap: anywhere;
4448
+ }
4449
+
4450
+ .active-patrol-main span {
4451
+ color: #94a3b8;
4452
+ display: block;
4453
+ font-size: 11px;
4454
+ line-height: 1.25;
4455
+ overflow-wrap: anywhere;
4456
+ }
4457
+
4458
+ .active-patrol-assignees {
4459
+ align-items: center;
4460
+ display: flex;
4461
+ gap: 6px;
4462
+ min-width: 0;
4463
+ }
4464
+
4465
+ .active-patrol-assignee {
4466
+ align-items: center;
4467
+ background: #f8fafc;
4468
+ border: 1px solid #e7edf3;
4469
+ border-radius: 999px;
4470
+ display: inline-flex;
4471
+ flex: 1 1 auto;
4472
+ gap: 6px;
4473
+ max-width: 100%;
4474
+ min-width: 0;
4475
+ padding: 2px 8px 2px 3px;
4476
+ }
4477
+
4478
+ .active-patrol-assignee span {
4479
+ color: #475569;
4480
+ font-size: 11px;
4481
+ font-weight: 600;
4482
+ line-height: 1.2;
4483
+ min-width: 0;
4484
+ overflow: hidden;
4485
+ text-overflow: ellipsis;
4486
+ white-space: nowrap;
4487
+ }
4488
+
4489
+ .active-patrol-statuses {
4490
+ align-items: center;
4491
+ display: flex;
4492
+ gap: 6px;
4493
+ justify-content: flex-start;
4494
+ min-width: 0;
4495
+ }
4496
+
4497
+ .active-patrol-status-pill {
4498
+ border-radius: 20px;
4499
+ font-size: 11px;
4500
+ font-weight: 600;
4501
+ line-height: 1.2;
4502
+ padding: 5px 10px;
4503
+ text-transform: capitalize;
4504
+ white-space: nowrap;
4505
+ }
4506
+
4507
+ .active-patrol-more-pill {
4508
+ align-items: center;
4509
+ background: #eef3f8;
4510
+ border: 1px solid #dbe4ed;
4511
+ border-radius: 999px;
4512
+ color: #607386;
4513
+ display: inline-flex;
4514
+ flex: 0 0 auto;
4515
+ font-size: 11px;
4516
+ font-weight: 800;
4517
+ height: 24px;
4518
+ justify-content: center;
4519
+ line-height: 1;
4520
+ min-width: 30px;
4521
+ padding: 0 8px;
4522
+ position: relative;
4523
+ white-space: nowrap;
4524
+ }
4525
+
4526
+ .active-patrol-action {
4527
+ justify-self: end;
4528
+ text-align: right;
4529
+ }
4530
+
4531
+ .active-patrol-tooltip::after {
4532
+ background: #0f2638;
4533
+ border-radius: 6px;
4534
+ bottom: calc(100% + 8px);
4535
+ box-shadow: 0 8px 24px rgba(15, 38, 56, 0.18);
4536
+ color: #ffffff;
4537
+ content: attr(data-tooltip);
4538
+ font-size: 11px;
4539
+ font-weight: 600;
4540
+ left: 50%;
4541
+ line-height: 1.35;
4542
+ max-width: 260px;
4543
+ opacity: 0;
4544
+ padding: 7px 9px;
4545
+ pointer-events: none;
4546
+ position: absolute;
4547
+ text-align: left;
4548
+ transform: translate(-50%, 4px);
4549
+ transition: opacity 0.14s ease, transform 0.14s ease;
4550
+ white-space: normal;
4551
+ width: max-content;
4552
+ z-index: 5;
4553
+ }
4554
+
4555
+ .active-patrol-tooltip:hover::after {
4556
+ opacity: 1;
4557
+ transform: translate(-50%, 0);
4558
+ }
4559
+
4560
+ @media (max-width: 960px) {
4561
+ .active-patrol-row {
4562
+ grid-template-columns: 120px minmax(0, 1fr) max-content max-content;
4563
+ }
4564
+ }
4565
+
4425
4566
  .figma-icon-badge {
4426
4567
  align-items: center;
4427
4568
  border-radius: 50%;
@@ -7,7 +7,7 @@
7
7
  <template v-if="fileTypes?.[x] === 'image'">
8
8
  <v-carousel-item height="100%" width="100%" rounded="lg">
9
9
  <v-row no-gutters class="w-100 h-100" align="center">
10
- <v-img :lazy-src="getFileUrl(x)" :src="getFileUrl(x)" width="70%" height="70%"
10
+ <v-img :lazy-src="getFinalUrl(x)" :src="getFinalUrl(x)" width="70%" height="70%"
11
11
  :alt="'Image Viewer Card -' + index"></v-img>
12
12
  </v-row>
13
13
  <template v-slot:placeholder>
@@ -21,7 +21,7 @@
21
21
  <v-carousel-item>
22
22
  <v-row no-gutters class="h-100 w-100" align="center" justify="center">
23
23
  <video width="80%" height="80%" controls>
24
- <source :src="getFileUrl(x)" />
24
+ <source :src="getFinalUrl(x)" />
25
25
  </video>
26
26
  </v-row>
27
27
  <template v-slot:placeholder>
@@ -78,11 +78,19 @@ const props = defineProps({
78
78
  files: {
79
79
  type: Array as PropType<string[]>,
80
80
  default: []
81
+ },
82
+ site: {
83
+ type: String,
84
+ default: ""
85
+ },
86
+ anpr: {
87
+ type: Boolean,
88
+ default: false
81
89
  }
82
90
  });
83
91
 
84
92
 
85
- const { getFileUrl, urlToFile } = useFile()
93
+ const { getFileUrl, urlToFile, getFileUrlAnpr } = useFile()
86
94
  const overlay = defineModel({ required: true, default: false });
87
95
  const activeIndex = ref(0);
88
96
  const fileTypes = ref<Record<string, "image" | "video" | "other">>({});
@@ -107,10 +115,13 @@ async function resolveFileTypes() {
107
115
 
108
116
  for (const x of props.files) {
109
117
  try {
110
- const url = getFileUrl(x);
118
+ let url = ""
119
+ if (props.anpr && props.site) {
120
+ url = getFileUrlAnpr(`${props.site}/${x}`)
121
+ } else url = getFileUrl(x);
111
122
  const file = await urlToFile(url, x);
112
123
  const type = file?.type;
113
-
124
+
114
125
 
115
126
  if (type?.startsWith("video")) fileTypes.value[x] = "video";
116
127
  else if (type?.startsWith("image")) fileTypes.value[x] = "image";
@@ -129,6 +140,13 @@ watch(
129
140
  );
130
141
 
131
142
 
143
+ const getFinalUrl = (id: string) => {
144
+ if (props.anpr && props.site) {
145
+ return getFileUrlAnpr(`${props.site}/${id}`)
146
+ } else return getFileUrl(id)
147
+ }
148
+
149
+
132
150
  </script>
133
151
 
134
152
  <style scoped>
@@ -304,31 +304,38 @@
304
304
 
305
305
  <v-divider />
306
306
 
307
- <v-card-actions class="pa-0">
308
- <v-btn
309
- color="grey"
310
- variant="flat"
311
- rounded="0"
312
- class="flex-grow-1 text-none"
313
- height="48"
314
- :disabled="rejectModal.submitting"
315
- @click="closeRejectDialog"
316
- >
317
- Cancel
318
- </v-btn>
319
- <v-btn
320
- color="error"
321
- variant="flat"
322
- rounded="0"
323
- class="flex-grow-1 text-none"
324
- height="48"
325
- :loading="rejectModal.submitting"
326
- :disabled="!isRejectModalValid || rejectModal.submitting"
327
- @click="submitRejectDialog"
328
- >
329
- Confirm
330
- </v-btn>
331
- </v-card-actions>
307
+ <v-toolbar class="pa-0" density="compact">
308
+ <v-row no-gutters>
309
+ <v-col cols="6" class="pa-0">
310
+ <v-btn
311
+ block
312
+ variant="text"
313
+ class="text-none"
314
+ size="large"
315
+ height="48"
316
+ :disabled="rejectModal.submitting"
317
+ @click="closeRejectDialog"
318
+ >
319
+ Cancel
320
+ </v-btn>
321
+ </v-col>
322
+
323
+ <v-col cols="6" class="pa-0">
324
+ <v-btn
325
+ block
326
+ variant="flat"
327
+ color="black"
328
+ class="text-none font-weight-bold rounded-0"
329
+ height="48"
330
+ :loading="rejectModal.submitting"
331
+ :disabled="!isRejectModalValid || rejectModal.submitting"
332
+ @click="submitRejectDialog"
333
+ >
334
+ Confirm
335
+ </v-btn>
336
+ </v-col>
337
+ </v-row>
338
+ </v-toolbar>
332
339
  </v-card>
333
340
  </v-dialog>
334
341
 
@@ -149,21 +149,23 @@
149
149
  </template>
150
150
 
151
151
  <template #footer>
152
- <v-btn
153
- variant="text"
154
- @click="confirmDialog = false"
155
- :disabled="updateLoading"
156
- >
157
- Close
158
- </v-btn>
159
- <v-btn
160
- color="primary"
161
- variant="flat"
162
- @click="handleUpdateMemberStatus"
163
- :loading="updateLoading"
164
- >
165
- {{ updateActionText }} Member </v-btn
166
- >r
152
+ <div class="d-flex justify-center ga-3 w-100">
153
+ <v-btn
154
+ variant="text"
155
+ @click="confirmDialog = false"
156
+ :disabled="updateLoading"
157
+ >
158
+ Close
159
+ </v-btn>
160
+ <v-btn
161
+ color="primary"
162
+ variant="flat"
163
+ @click="handleUpdateMemberStatus"
164
+ :loading="updateLoading"
165
+ >
166
+ {{ updateActionText }} Member
167
+ </v-btn>
168
+ </div>
167
169
  </template>
168
170
  </ConfirmDialog>
169
171