@7365admin1/layer-common 3.1.2-staging.38 → 3.1.2

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.
Files changed (32) hide show
  1. package/.github/workflows/publish-staging.yml +0 -11
  2. package/CHANGELOG.md +6 -0
  3. package/components/AddEqupmentForm.vue +1 -52
  4. package/components/CameraMain.vue +6 -5
  5. package/components/DashboardMain.vue +87 -231
  6. package/components/EquipmentManagementMain.vue +0 -56
  7. package/components/InvitationMain.vue +3 -4
  8. package/components/ManageChecklistMain.vue +38 -44
  9. package/components/Nfc/NFCPatrolReportMain.vue +38 -146
  10. package/components/Nfc/PatrolReport/DailyReportTab.vue +6 -10
  11. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +9 -19
  12. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +0 -20
  13. package/components/Nfc/PatrolReport/PatrolReportTab.vue +3 -5
  14. package/components/Nfc/PatrolReport/ReportFilters.vue +20 -52
  15. package/components/Nfc/PatrolReport/SummaryReportTable.vue +0 -22
  16. package/components/PhotoUpload.vue +4 -19
  17. package/components/ServiceProviderMain.vue +25 -59
  18. package/components/VisitorForm.vue +18 -78
  19. package/components/VisitorManagement.vue +16 -12
  20. package/composables/useCleaningSchedulePermission.ts +51 -16
  21. package/composables/useEquipment.ts +0 -2
  22. package/composables/useLocalAuth.ts +1 -1
  23. package/composables/useNFCPatrolDailyReport.ts +4 -3
  24. package/composables/useNFCPatrolMonthlyReport.ts +30 -62
  25. package/composables/useNFCPatrolReport.ts +0 -2
  26. package/composables/useServiceProvider.ts +0 -21
  27. package/package.json +1 -1
  28. package/types/customer.d.ts +1 -2
  29. package/types/equipment.d.ts +1 -5
  30. package/types/nfc-patrol-report.ts +1 -19
  31. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +0 -69
  32. package/components/Nfc/PatrolReport/ReportDatePicker.vue +0 -61
@@ -34,14 +34,3 @@ 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"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 3.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 56e957d: Update Package Version
8
+
3
9
  ## 3.1.1
4
10
 
5
11
  ### Patch Changes
@@ -12,24 +12,6 @@
12
12
  <v-card-text class="pa-4">
13
13
  <v-form ref="formRef" v-model="valid">
14
14
  <v-row no-gutters class="pa-0">
15
- <v-col cols="12" class="pa-0 mb-2">
16
- <PhotoUpload
17
- v-model="attachmentPhotos"
18
- v-model:photo-ids="attachmentIds"
19
- title="Photo"
20
- @error="photoUploadError = $event"
21
- />
22
-
23
- <v-alert
24
- v-if="photoUploadError"
25
- type="error"
26
- density="compact"
27
- variant="tonal"
28
- class="mt-2"
29
- >
30
- {{ photoUploadError }}
31
- </v-alert>
32
- </v-col>
33
15
  <v-col cols="12" class="pa-0 mb-2">
34
16
  <InputLabel for="name" title="Name" required class="mb-1" />
35
17
  <v-text-field
@@ -105,10 +87,6 @@ const unitOfMeasurementModel = defineModel("unitOfMeasurement", {
105
87
  type: String,
106
88
  default: "",
107
89
  });
108
- const attachmentModel = defineModel("attachment", {
109
- type: String,
110
- default: "",
111
- });
112
90
  const showDialog = defineModel({ type: Boolean, default: false });
113
91
 
114
92
  const prop = defineProps({
@@ -127,9 +105,6 @@ const emit = defineEmits(["saved", "close"]);
127
105
  const formRef = ref<any>(null);
128
106
  const valid = ref(false);
129
107
  const submitting = ref(false);
130
- const attachmentPhotos = ref<string[]>([]);
131
- const attachmentIds = ref<string[]>([]);
132
- const photoUploadError = ref("");
133
108
 
134
109
  const { requiredRule } = useUtils();
135
110
 
@@ -149,29 +124,6 @@ const unitOfMeasurementOptions = [
149
124
  watchEffect(() => {
150
125
  nameModel.value = prop.equipmentData?.name || "";
151
126
  unitOfMeasurementModel.value = prop.equipmentData?.unitOfMeasurement || "";
152
- attachmentModel.value = prop.equipmentData?.attachment || "";
153
- });
154
-
155
- watch(
156
- attachmentIds,
157
- (photoIds) => {
158
- const [attachmentId] = photoIds;
159
- attachmentModel.value = attachmentId || "";
160
-
161
- if (photoIds.length > 1) {
162
- attachmentIds.value = attachmentId ? [attachmentId] : [];
163
- attachmentPhotos.value = attachmentPhotos.value.slice(0, 1);
164
- }
165
- },
166
- { deep: true }
167
- );
168
-
169
- watch(showDialog, (isOpen) => {
170
- if (!isOpen) {
171
- attachmentPhotos.value = [];
172
- attachmentIds.value = [];
173
- photoUploadError.value = "";
174
- }
175
127
  });
176
128
 
177
129
  function close() {
@@ -195,15 +147,12 @@ async function submit() {
195
147
 
196
148
  emit("saved", {
197
149
  name: nameModel.value,
150
+
198
151
  unitOfMeasurement: unitOfMeasurementModel.value,
199
- attachment: attachmentModel.value,
200
152
  });
201
153
 
202
154
  nameModel.value = "";
203
155
  unitOfMeasurementModel.value = "";
204
- attachmentModel.value = "";
205
- attachmentPhotos.value = [];
206
- attachmentIds.value = [];
207
156
 
208
157
  showDialog.value = false;
209
158
  emit("close");
@@ -28,6 +28,7 @@
28
28
  :items="items"
29
29
  fixed-header
30
30
  hide-default-footer
31
+ hide-default-header
31
32
  @click:row="handleRowClick"
32
33
  style="max-height: 300px"
33
34
  >
@@ -280,10 +281,10 @@ const prop = defineProps({
280
281
  headers: {
281
282
  type: Array as PropType<Array<any>>,
282
283
  default: () => [
283
- { title: "Host", key: "host" },
284
- { title: "Category", key: "category" },
285
- // { title: "Guard House", key: "guardPost" },
286
- // { title: "Actions", key: "action", sortable: false },
284
+ { text: "Host", value: "host" },
285
+ { text: "Category", value: "category" },
286
+ { text: "Guard House", value: "guardPost" },
287
+ { text: "Actions", value: "action", sortable: false },
287
288
  ],
288
289
  },
289
290
  readOnly: {
@@ -295,7 +296,7 @@ const prop = defineProps({
295
296
  const computedHeaders = computed(() => {
296
297
  if (prop.type === "ip") {
297
298
  const base = [...prop.headers];
298
- base.splice(0, 0, { title: "Name", key: "name" });
299
+ base.splice(0, 0, { text: "Name", value: "name" });
299
300
  return base;
300
301
  }
301
302
  return prop.headers;
@@ -984,44 +984,60 @@
984
984
  <div
985
985
  v-for="item in formattedActivePatrolItems"
986
986
  :key="item.id || item._id"
987
- class="active-patrol-row"
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
+ "
988
996
  >
989
- <div class="active-patrol-main">
990
- <strong>{{ item.title || item.name }}</strong>
991
- <span>{{ item.subtitle || item.location || "" }}</span>
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>
992
1011
  </div>
993
1012
 
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>
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>
1006
1023
  </div>
1007
1024
 
1008
- <div class="active-patrol-statuses">
1025
+ <div style="flex: 1; display: flex; justify-content: center">
1009
1026
  <div
1010
- class="active-patrol-status-pill"
1011
- :style="getPatrolStatusStyle(item.primaryStatus)"
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
+ "
1012
1035
  >
1013
- {{ item.primaryStatus }}
1036
+ {{ item.status }}
1014
1037
  </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>
1022
1038
  </div>
1023
1039
 
1024
- <div class="active-patrol-action">
1040
+ <div style="flex: 0 0 auto; text-align: right">
1025
1041
  <v-btn
1026
1042
  variant="outlined"
1027
1043
  class="text-none font-weight-medium"
@@ -1529,16 +1545,30 @@ const { userAppRole } = useLocalSetup();
1529
1545
  const { downloadPDF } = usePDFDownload();
1530
1546
  const route = useRoute();
1531
1547
 
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
+
1532
1562
  const propertyWidgetPermissions: Record<string, string[]> = {
1533
1563
  workOrders: [
1534
- "work_orders:see-all-work-orders",
1564
+ "workOrder:see-all-work-orders",
1535
1565
  "work-order:see-all-work-orders",
1536
1566
  ],
1537
1567
  incidents: ["incident-reports:see-incident-reports"],
1538
1568
  visitors: ["visitorManagement:see-all-visitor"],
1539
1569
  facilityBookings: ["facility-booking-mgmt:see-all-facility-booking"],
1540
1570
  activity: [
1541
- "work_orders:see-all-work-orders",
1571
+ "workOrder:see-all-work-orders",
1542
1572
  "work-order:see-all-work-orders",
1543
1573
  ],
1544
1574
  upcomingEvents: ["event-mgmt:see-all-event"],
@@ -1548,7 +1578,7 @@ const propertyWidgetPermissions: Record<string, string[]> = {
1548
1578
  "facility-booking-mgmt:see-all-facility-booking",
1549
1579
  ],
1550
1580
  workOrderStatus: [
1551
- "work_orders:see-all-work-orders",
1581
+ "workOrder:see-all-work-orders",
1552
1582
  "work-order:see-all-work-orders",
1553
1583
  ],
1554
1584
  feedbacks: ["feedbacks:see-all-feedback"],
@@ -1557,30 +1587,27 @@ const propertyWidgetPermissions: Record<string, string[]> = {
1557
1587
 
1558
1588
  const moduleWidgetPermissions: Record<string, string[]> = {
1559
1589
  openWorkOrder: [
1560
- "work_orders:see-all-work-orders",
1561
1590
  "work_order:see-all-work-orders",
1562
1591
  "work-order:see-all-work-orders",
1563
1592
  "work-orders:see-all-work-orders",
1564
1593
  ],
1565
1594
  workOrders: [
1566
- "work_orders:see-all-work-orders",
1567
1595
  "work_order:see-all-work-orders",
1568
1596
  "work-order:see-all-work-orders",
1569
1597
  "work-orders:see-all-work-orders",
1570
1598
  ],
1571
1599
  activity: [
1572
- "work_orders:see-all-work-orders",
1573
1600
  "work_order:see-all-work-orders",
1574
1601
  "work-order:see-all-work-orders",
1575
1602
  "work-orders:see-all-work-orders",
1576
- "work_orders:see-all-work-orders",
1577
1603
  ],
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",
1604
+ taskSchedule: [
1605
+ "cleaning-schedule-mgmt:see-all-schedules",
1606
+ "schedule-task-mgmt:see-all-schedule-tasks",
1607
+ ],
1608
+ taskCompleted: [
1609
+ "cleaning-schedule-mgmt:see-all-schedules",
1610
+ "schedule-task-mgmt:see-all-schedule-tasks",
1584
1611
  ],
1585
1612
  staffStatus: ["attendance-mgmt:see-all-attendance"],
1586
1613
  attendance: ["attendance-mgmt:see-all-attendance"],
@@ -1603,14 +1630,9 @@ const moduleWidgetPermissions: Record<string, string[]> = {
1603
1630
  activePatrol: ["virtual-patrol:see-all-virtual-patrol-logs"],
1604
1631
  };
1605
1632
 
1606
- function hasSchedulePermission(permissions: string[], action: string) {
1607
- return permissions.some((permission) =>
1608
- permission.endsWith(`-schedule-mgmt:${action}`)
1609
- );
1610
- }
1611
-
1612
1633
  function canViewWidget(key: string) {
1613
1634
  const permissions = userAppRole.value?.permissions;
1635
+ const dashboardPermission = dashboardWidgetPermissions[key];
1614
1636
  const modulePermissions = isPropertyMode.value
1615
1637
  ? propertyWidgetPermissions[key]
1616
1638
  : moduleWidgetPermissions[key];
@@ -1618,22 +1640,20 @@ function canViewWidget(key: string) {
1618
1640
  if (!permissions) return true;
1619
1641
  if (permissions.includes("*")) return true;
1620
1642
 
1621
- if (
1622
- !isPropertyMode.value &&
1623
- ["taskSchedule", "taskCompleted"].includes(key)
1624
- ) {
1625
- return hasSchedulePermission(permissions, "see-all-schedules");
1626
- }
1627
-
1628
1643
  if (isPropertyMode.value && modulePermissions) {
1629
1644
  return modulePermissions.some((permission) =>
1630
1645
  permissions.includes(permission)
1631
1646
  );
1632
1647
  }
1633
1648
 
1634
- if (modulePermissions) {
1635
- return modulePermissions.some((permission) =>
1636
- permissions.includes(permission)
1649
+ if (dashboardPermission || modulePermissions) {
1650
+ return (
1651
+ (dashboardPermission && permissions.includes(dashboardPermission)) ||
1652
+ Boolean(
1653
+ modulePermissions?.some((permission) =>
1654
+ permissions.includes(permission)
1655
+ )
1656
+ )
1637
1657
  );
1638
1658
  }
1639
1659
 
@@ -2751,23 +2771,18 @@ const activePatrolItems = computed<any[]>(
2751
2771
 
2752
2772
  const formattedActivePatrolItems = computed<any[]>(() => {
2753
2773
  return activePatrolItems.value.map((item) => {
2754
- const statuses = normalizeListValue(item.status || "Pending");
2755
- const assignees = normalizeListValue(
2756
- item.person || item.assignedTo || "Unassigned"
2757
- );
2758
-
2774
+ let status = item.status;
2775
+ if (Array.isArray(status)) {
2776
+ status = status.join(", ");
2777
+ }
2778
+ let person = item.person || item.assignedTo || "Unassigned";
2779
+ if (Array.isArray(person)) {
2780
+ person = person.join(", ");
2781
+ }
2759
2782
  return {
2760
2783
  ...item,
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),
2784
+ status,
2785
+ person,
2771
2786
  };
2772
2787
  });
2773
2788
  });
@@ -3937,20 +3952,6 @@ function normalizeStatus(value: any) {
3937
3952
  return text.replace(/[_-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3938
3953
  }
3939
3954
 
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
-
3954
3955
  function getStatusTone(status: any) {
3955
3956
  const s = String(status || "").toLowerCase();
3956
3957
  if (
@@ -4418,151 +4419,6 @@ function formatDashboardTime(value?: string) {
4418
4419
  border: 2px solid #f9c97c;
4419
4420
  }
4420
4421
 
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
-
4566
4422
  .figma-icon-badge {
4567
4423
  align-items: center;
4568
4424
  border-radius: 50%;