@7365admin1/layer-common 4.90.2-staging.491 → 4.90.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 (77) hide show
  1. package/CHANGELOG.md +10 -4
  2. package/assets/css/primitives.css +0 -57
  3. package/assets/css/screens.css +4 -8
  4. package/components/AccessCardDetailsDialog.vue +6 -59
  5. package/components/AccessCardPreviewDialog.vue +53 -371
  6. package/components/AppSelect.vue +4 -143
  7. package/components/BuildingUnitFormEdit.vue +0 -6
  8. package/components/Dialog/UpdateMoreAction.vue +1 -11
  9. package/components/DocumentForm.vue +112 -407
  10. package/components/DocumentManagement.vue +12 -213
  11. package/components/Facility/BookingSetup.vue +0 -63
  12. package/components/HidAccessLogDashboard.vue +17 -74
  13. package/components/HidIntercomManagement.vue +14 -183
  14. package/components/HidQrCodeConfiguration.vue +195 -17
  15. package/components/HidReaderManagement.vue +0 -398
  16. package/components/HidReaderUserRoster.vue +3 -44
  17. package/components/HidUserEnrollment.vue +292 -2255
  18. package/components/InventoryItemDetail.vue +1 -2
  19. package/components/InventoryLinesDialog.vue +13 -91
  20. package/components/InventoryPhotoInput.vue +6 -44
  21. package/components/InventoryReportsTab.vue +3 -23
  22. package/components/InventoryRequestsTab.vue +4 -27
  23. package/components/InventoryStockTab.vue +0 -3
  24. package/components/InvitationClientForm.vue +1 -19
  25. package/components/InvitationForm.vue +4 -31
  26. package/components/InvitationMain.vue +22 -124
  27. package/components/MemberInformation.vue +4 -4
  28. package/components/PassInformation.vue +2 -2
  29. package/components/SiteSettings.vue +0 -48
  30. package/components/TableMain.vue +7 -22
  31. package/components/VehicleManagement.vue +5 -80
  32. package/components/VisitorForm.vue +19 -65
  33. package/components/VisitorManagement.vue +5 -267
  34. package/composables/useAccessManagement.ts +1 -17
  35. package/composables/useDocument.ts +0 -7
  36. package/composables/useFacility.ts +0 -11
  37. package/composables/useHidAmico.ts +0 -165
  38. package/composables/useHidNavigation.ts +1 -25
  39. package/composables/useInventory.ts +1 -4
  40. package/composables/useMember.ts +0 -12
  41. package/composables/useNFCPatrolReportFilters.ts +3 -21
  42. package/composables/useNFCPatrolRoute.ts +1 -8
  43. package/composables/usePeople.ts +0 -19
  44. package/composables/useServiceProvider.ts +2 -45
  45. package/composables/useSettingsPermission.ts +0 -14
  46. package/composables/useUser.ts +2 -6
  47. package/composables/useVehicle.ts +1 -40
  48. package/composables/useVerification.ts +0 -3
  49. package/middleware/01.auth.ts +7 -3
  50. package/package.json +1 -1
  51. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +1 -1
  52. package/pages/[org]/[site]/access-mgmt/hid-cards/index.vue +1 -1
  53. package/pages/[org]/[site]/access-mgmt/hid-readers/index.vue +1 -1
  54. package/types/document.d.ts +0 -14
  55. package/types/facility.d.ts +0 -4
  56. package/types/inventory.d.ts +0 -27
  57. package/types/member.d.ts +0 -4
  58. package/types/people.d.ts +1 -28
  59. package/types/service-provider.d.ts +0 -5
  60. package/types/site.d.ts +1 -7
  61. package/components/BuildingUnitManagement.vue +0 -298
  62. package/components/HidAccessPermissions.vue +0 -424
  63. package/components/HidProfileQrCode.vue +0 -332
  64. package/components/InventoryPoDialog.vue +0 -111
  65. package/components/VehicleQrStickerDialog.vue +0 -249
  66. package/composables/useHidReaderSelection.ts +0 -50
  67. package/composables/useSiteCategory.ts +0 -45
  68. package/pages/[org]/[site]/access-mgmt/hid-qr-code/index.vue +0 -23
  69. package/utils/hid-enrolment-subject.ts +0 -344
  70. package/utils/hid-permission-assignments.ts +0 -198
  71. package/utils/hid-reader-selection.ts +0 -52
  72. package/utils/inventory-receiving.ts +0 -90
  73. package/utils/invite-group.ts +0 -61
  74. package/utils/invite-sites.ts +0 -25
  75. package/utils/occupancy-role.ts +0 -109
  76. package/utils/service-type.ts +0 -30
  77. package/utils/vehicle-qr-sticker.ts +0 -184
@@ -1,177 +1,45 @@
1
1
  <template>
2
2
  <v-card width="100%" class="screen-modal">
3
- <v-card-title class="screen-modal__title">{{ heading }}</v-card-title>
3
+ <v-card-title class="text-capitalize">{{ prop.mode }} Document</v-card-title>
4
4
 
5
5
  <v-card-text class="screen-modal__body pa-0">
6
- <!--
7
- EDIT: metadata only. The design deliberately shows the attachment as a
8
- read-only chip here - swapping the file is what Update- Document is for,
9
- and expiry is only set on upload or renewal, so neither control appears.
10
- -->
11
- <template v-if="isEdit">
12
- <v-col cols="12" class="px-6 pt-4">
13
- <InputLabel title="Document Attachment" />
14
- <div class="doc-form__chip">
15
- <v-icon size="20" class="mr-2">mdi-file-document-outline</v-icon>
16
- <span class="text-body-2">{{ existingFileName || "—" }}</span>
17
- </div>
6
+ <v-form v-model="validForm" :disabled="disable">
7
+ <v-col cols="12" class="px-6">
8
+ <InputLabel class="text-capitalize" title="Document Attachment" />
9
+ <InputFileV2
10
+ v-model="document.attachment"
11
+ :multiple="false"
12
+ :max-length="10"
13
+ title="Upload Files"
14
+ accept=".pdf,.csv,.doc,.docx"
15
+ />
18
16
  </v-col>
19
17
 
20
18
  <v-col cols="12" class="px-6">
21
- <InputLabel title="Document Title" required />
19
+ <InputLabel class="text-capitalize" title="Document Name" />
22
20
  <v-text-field
23
- v-model.trim="editTitle"
21
+ v-model.trim="document.name"
24
22
  class="filter-field"
25
23
  density="comfortable"
26
- placeholder="Company Policy"
27
- hide-details="auto"
28
- />
24
+ ></v-text-field>
29
25
  </v-col>
30
26
 
31
- <v-col cols="12" class="px-6 pb-2">
32
- <InputLabel title="Remarks (Optional)" />
33
- <v-textarea
34
- v-model.trim="editRemarks"
35
- class="filter-field"
36
- density="comfortable"
37
- rows="3"
38
- placeholder="Enter remarks"
39
- hide-details="auto"
40
- />
27
+ <v-col cols="12">
28
+ <v-row no-gutters>
29
+ <v-col cols="12" class="text-center">
30
+ <span
31
+ class="text-none text-subtitle-2 font-weight-medium text-error"
32
+ >
33
+ {{ message }}
34
+ </span>
35
+ </v-col>
36
+ </v-row>
41
37
  </v-col>
42
- </template>
43
-
44
- <!-- ADD and UPDATE: a drop zone, then one configurable row per file. -->
45
- <template v-else>
46
- <v-col cols="12" class="px-6 pt-4">
47
- <InputLabel title="Document Attachment" />
48
-
49
- <!--
50
- Hand-rolled rather than `InputFileV2`, which renders its own chip
51
- list from internal state and only ever pushes ids outward. The
52
- design puts that list in the table below with a per-row delete, and
53
- a delete there could not reach back into the uploader to drop the
54
- chip - the two lists would disagree on what is about to be saved.
55
- -->
56
- <div
57
- class="doc-form__drop"
58
- :class="{ 'doc-form__drop--over': dragging }"
59
- role="button"
60
- tabindex="0"
61
- @click="pickFiles"
62
- @keydown.enter.prevent="pickFiles"
63
- @keydown.space.prevent="pickFiles"
64
- @dragover.prevent="dragging = true"
65
- @dragleave.prevent="dragging = false"
66
- @drop.prevent="onDrop"
67
- >
68
- <v-progress-circular v-if="uploading" indeterminate size="24" />
69
- <template v-else>
70
- <v-icon size="24" class="mr-2">mdi-cloud-upload-outline</v-icon>
71
- <span class="text-body-2">{{ dropLabel }}</span>
72
- </template>
73
- </div>
74
-
75
- <input
76
- ref="fileInput"
77
- type="file"
78
- class="d-none"
79
- :accept="ACCEPT"
80
- :multiple="isAdd"
81
- @change="onPick"
82
- />
83
- </v-col>
84
-
85
- <v-col v-if="rows.length" cols="12" class="px-6 pb-2">
86
- <div class="d-flex align-center justify-space-between">
87
- <span class="text-body-2">{{ selectionSummary }}</span>
88
- <v-btn
89
- v-if="isAdd"
90
- variant="flat"
91
- class="text-none screen-btn-primary"
92
- prepend-icon="mdi-plus"
93
- :disabled="uploading"
94
- @click="pickFiles"
95
- >
96
- Add more files
97
- </v-btn>
98
- </div>
99
- </v-col>
100
-
101
- <v-col v-if="rows.length" cols="12" class="pa-0">
102
- <v-table class="doc-form__table" density="comfortable">
103
- <thead>
104
- <tr>
105
- <th>Original File Name</th>
106
- <th>Custom Name</th>
107
- <th>Remarks</th>
108
- <th>{{ isAdd ? "Expiry Date (optional)" : "Expiry Date" }}</th>
109
- <th class="text-center">Action</th>
110
- </tr>
111
- </thead>
112
- <tbody>
113
- <tr v-for="row in rows" :key="row.id">
114
- <td>
115
- <div class="d-flex align-center ga-2">
116
- <v-icon size="20">mdi-file-document-outline</v-icon>
117
- <span>{{ row.originalName }}</span>
118
- </div>
119
- </td>
120
- <td>
121
- <v-text-field
122
- v-model.trim="row.customName"
123
- class="filter-field"
124
- density="compact"
125
- placeholder="Company Policy"
126
- hide-details
127
- />
128
- </td>
129
- <td>
130
- <v-text-field
131
- v-model.trim="row.remarks"
132
- class="filter-field"
133
- density="compact"
134
- placeholder="Enter remarks"
135
- hide-details
136
- />
137
- </td>
138
- <td>
139
- <!--
140
- No `min`. A past date is legitimate on both paths: an estate
141
- may file a contract that already lapsed, and a renewal that
142
- does not move the date forward is defined to leave the
143
- document on the Expired tab rather than be rejected.
144
- -->
145
- <InputDatePicker
146
- v-model="row.expiryDate"
147
- density="compact"
148
- clearable
149
- hide-details
150
- />
151
- </td>
152
- <td class="text-center">
153
- <v-btn
154
- icon="mdi-trash-can-outline"
155
- variant="text"
156
- size="small"
157
- density="comfortable"
158
- aria-label="Remove file"
159
- @click="removeRow(row.id)"
160
- />
161
- </td>
162
- </tr>
163
- </tbody>
164
- </v-table>
165
- </v-col>
166
- </template>
167
-
168
- <v-col cols="12" class="pt-0">
169
- <p v-if="message" class="text-error text-center text-body-2 mb-0">
170
- {{ message }}
171
- </p>
172
- </v-col>
38
+ </v-form>
173
39
  </v-card-text>
174
40
 
41
+ <!-- Was a `v-toolbar`: a grey slab with square (`tile`) buttons and a
42
+ hardcoded `color="black"` primary. The gate on Submit is unchanged. -->
175
43
  <v-card-actions class="screen-modal__footer">
176
44
  <v-row no-gutters>
177
45
  <v-col cols="6" class="pr-2">
@@ -179,8 +47,8 @@
179
47
  block
180
48
  variant="outlined"
181
49
  class="text-none screen-btn-ghost"
182
- :disabled="saving"
183
50
  @click="cancel"
51
+ :disabled="disable"
184
52
  >
185
53
  Cancel
186
54
  </v-btn>
@@ -191,38 +59,24 @@
191
59
  block
192
60
  variant="flat"
193
61
  class="text-none screen-btn-primary"
194
- :disabled="!canSubmit || saving"
195
- :loading="saving"
62
+ :disabled="
63
+ !validForm ||
64
+ disable ||
65
+ document.attachment.length == 0 ||
66
+ document.name == ''
67
+ "
196
68
  @click="submit"
69
+ :loading="disable"
197
70
  >
198
- {{ submitLabel }}
71
+ Submit
199
72
  </v-btn>
200
73
  </v-col>
201
74
  </v-row>
202
75
  </v-card-actions>
203
76
  </v-card>
204
77
  </template>
205
-
206
78
  <script setup lang="ts">
207
- /** The upload filter. Kept beside `deriveTypeFromFilename`, which must agree. */
208
- const ACCEPT = ".pdf,.csv,.doc,.docx";
209
-
210
- type DocumentRow = {
211
- /** Stored file id, returned by `addFile`. Also the row key. */
212
- id: string;
213
- originalName: string;
214
- customName: string;
215
- remarks: string;
216
- /** `YYYY-MM-DD` from `InputDatePicker`, or null for "never expires". */
217
- expiryDate: string | null;
218
- };
219
-
220
79
  const prop = defineProps({
221
- /**
222
- * "add" uploads any number of new documents, "update" replaces the file on
223
- * one existing document and re-dates it, "edit" changes title and remarks
224
- * only. They are three dialogs in the design and three payloads here.
225
- */
226
80
  mode: {
227
81
  type: String,
228
82
  default: "add",
@@ -244,55 +98,26 @@ const prop = defineProps({
244
98
  const emit = defineEmits(["cancel", "success"]);
245
99
 
246
100
  const { add, updateById } = useDocument();
247
- const { addFile, getFileById } = useFile();
248
-
249
- const isAdd = computed(() => prop.mode === "add");
250
- const isEdit = computed(() => prop.mode === "edit");
251
- const isUpdate = computed(() => prop.mode === "update");
252
-
253
- const heading = computed(() => {
254
- if (isEdit.value) return "Edit Document";
255
- if (isUpdate.value) return "Update- Document";
256
- return "Add Document";
257
- });
258
-
259
- const submitLabel = computed(() => (isEdit.value ? "Apply" : "Submit"));
260
- const dropLabel = computed(() => (isAdd.value ? "Select Files" : "Select File"));
101
+ const { getFileById } = useFile();
261
102
 
262
- const rows = ref<DocumentRow[]>([]);
263
- const fileInput = ref<HTMLInputElement | null>(null);
264
- const dragging = ref(false);
265
- const uploading = ref(false);
266
- const saving = ref(false);
103
+ const validForm = ref(false);
104
+ const disable = ref(false);
267
105
  const message = ref("");
268
106
 
269
- const editTitle = ref("");
270
- const editRemarks = ref("");
271
- const existingFileName = ref("");
107
+ const document = ref<Record<string, any>>({
108
+ name: "",
109
+ attachment: [],
110
+ type: "",
111
+ });
272
112
 
273
113
  const route = useRoute();
274
114
  const siteId = route.params.site as string;
275
115
  const orgId = route.params.org as string;
276
116
  const routeParentId = computed(() =>
277
- typeof route.query.parentId === "string" ? route.query.parentId : "",
117
+ typeof route.query.parentId === "string" ? route.query.parentId : ""
278
118
  );
279
119
  const effectiveParentId = computed(() => prop.parentId || routeParentId.value);
280
120
 
281
- const selectionSummary = computed(() =>
282
- rows.value.length === 1 ? "1 file selected" : `${rows.value.length} files selected`,
283
- );
284
-
285
- /**
286
- * Every row needs a name, because `name` is what the list draws and what the
287
- * server requires. Blank-but-present rows are the realistic mistake, so the
288
- * gate is on trimmed emptiness rather than on the form's validity flag.
289
- */
290
- const canSubmit = computed(() => {
291
- if (uploading.value) return false;
292
- if (isEdit.value) return editTitle.value.trim().length > 0;
293
- return rows.value.length > 0 && rows.value.every((r) => r.customName.trim());
294
- });
295
-
296
121
  function getAttachmentId(attachment: any): string {
297
122
  if (!attachment) return "";
298
123
  if (Array.isArray(attachment)) {
@@ -305,222 +130,102 @@ function getAttachmentId(attachment: any): string {
305
130
  return String(attachment?._id || attachment?.id || "");
306
131
  }
307
132
 
308
- function deriveTypeFromFilename(name = "") {
309
- const n = String(name).toLowerCase();
310
- if (n.endsWith(".pdf")) return "pdf";
311
- if (n.endsWith(".csv")) return "csv";
312
- if (n.endsWith(".doc") || n.endsWith(".docx")) return "doc";
313
- return "";
314
- }
315
-
316
- /** `Contract.doc` -> `Contract`, so the name field starts somewhere sensible. */
317
- function stripExtension(name: string) {
318
- const dot = name.lastIndexOf(".");
319
- return dot > 0 ? name.slice(0, dot) : name;
320
- }
321
-
322
- /** `2026-12-13` -> a date-only ISO instant the server's `Joi.date()` accepts. */
323
- function toIsoOrNull(value: string | null) {
324
- return value ? new Date(`${value}T00:00:00.000Z`).toISOString() : null;
325
- }
326
-
327
- /** An ISO instant from the server -> the `YYYY-MM-DD` the picker binds to. */
328
- function toPickerDate(value: unknown) {
329
- if (!value) return null;
330
- const d = new Date(value as string);
331
- return Number.isNaN(d.getTime()) ? null : d.toISOString().slice(0, 10);
332
- }
333
-
334
- const existingAttachmentId = computed(() =>
335
- getAttachmentId(prop.document?.attachment),
336
- );
337
-
338
- onMounted(async () => {
339
- if (!isEdit.value && !isUpdate.value) return;
340
-
341
- editTitle.value = prop.document?.name ?? "";
342
- editRemarks.value = String((prop.document as any)?.remarks ?? "");
343
-
344
- const id = existingAttachmentId.value;
345
- if (!id) return;
346
-
347
- try {
348
- const file = await getFileById(id);
349
- existingFileName.value = file?.data?.name ?? id;
350
- } catch {
351
- // The name is decoration on a read-only chip; the id still saves.
352
- existingFileName.value = id;
353
- }
354
- });
355
-
356
- function pickFiles() {
357
- if (uploading.value) return;
358
- fileInput.value?.click();
359
- }
360
-
361
- function onDrop(event: DragEvent) {
362
- dragging.value = false;
363
- const files = Array.from(event.dataTransfer?.files ?? []);
364
- if (files.length) void uploadFiles(files);
133
+ if (prop.mode === "edit") {
134
+ document.value.name = prop.document.name;
135
+
136
+ const extractedId = getAttachmentId(prop.document.attachment);
137
+ document.value.attachment = extractedId ? [extractedId] : [];
138
+
139
+ document.value.type = prop.document.type;
365
140
  }
366
141
 
367
- function onPick(event: Event) {
368
- const input = event.target as HTMLInputElement;
369
- const files = Array.from(input.files ?? []);
370
- // Cleared so re-picking the same file fires `change` again.
371
- input.value = "";
372
- if (files.length) void uploadFiles(files);
373
- }
142
+ watch(
143
+ () => document.value.attachment,
144
+ async (newVal, oldVal) => {
145
+ const newId = Array.isArray(newVal) ? newVal[0] : newVal;
146
+ const oldId = Array.isArray(oldVal) ? oldVal[0] : oldVal;
374
147
 
375
- async function uploadFiles(files: File[]) {
376
- message.value = "";
377
- uploading.value = true;
378
-
379
- try {
380
- // Update replaces one file, so a second pick supersedes the first rather
381
- // than queueing behind it.
382
- const queue = isAdd.value ? files : files.slice(0, 1);
383
-
384
- for (const file of queue) {
385
- if (!deriveTypeFromFilename(file.name)) {
386
- message.value = "Unsupported file type. Allowed: pdf, csv, doc, docx.";
387
- continue;
388
- }
389
-
390
- const stored = await addFile(file);
391
- if (!stored?.id) {
392
- message.value = "Upload failed. Please try again.";
393
- continue;
394
- }
395
-
396
- const row: DocumentRow = {
397
- id: stored.id,
398
- originalName: file.name,
399
- customName: stripExtension(file.name),
400
- remarks: "",
401
- expiryDate: null,
402
- };
403
-
404
- if (isAdd.value) {
405
- rows.value = [...rows.value, row];
406
- } else {
407
- // Carry the existing document's title and expiry onto the replacement,
408
- // so a renewal that only swaps the file keeps its identity.
409
- row.customName = editTitle.value || row.customName;
410
- row.remarks = editRemarks.value;
411
- row.expiryDate = toPickerDate((prop.document as any)?.expiryDate);
412
- rows.value = [row];
148
+ if (newId && newId !== oldId) {
149
+ const fileId = newId;
150
+ const fileData = await getFileById(fileId);
151
+ if (fileData) {
152
+ document.value.name = fileData.data.name;
413
153
  }
414
154
  }
415
- } catch (error: any) {
416
- message.value = error?.response?._data?.message || "Upload failed.";
417
- } finally {
418
- uploading.value = false;
419
155
  }
420
- }
156
+ );
421
157
 
422
- function removeRow(id: string) {
423
- rows.value = rows.value.filter((row) => row.id !== id);
158
+ function deriveTypeFromFilename(name = "") {
159
+ const n = String(name).toLowerCase();
160
+ if (n.endsWith(".pdf")) return "pdf";
161
+ if (n.endsWith(".csv")) return "csv";
162
+ if (n.endsWith(".doc") || n.endsWith(".docx")) return "doc";
163
+ return "";
424
164
  }
425
165
 
426
166
  function cancel() {
167
+ // createMore.value = false;
427
168
  message.value = "";
428
169
  emit("cancel");
429
170
  }
430
171
 
431
172
  async function submit() {
432
- saving.value = true;
433
- message.value = "";
434
-
173
+ disable.value = true;
435
174
  try {
436
- if (isAdd.value) {
437
- await add({
438
- // `name` is required on the envelope even when every item names
439
- // itself; the first row stands in for it.
440
- name: rows.value[0].customName,
175
+ if (prop.mode === "add") {
176
+ const fileId = Array.isArray(document.value.attachment)
177
+ ? document.value.attachment[0]
178
+ : document.value.attachment;
179
+
180
+ const fileData = await getFileById(fileId);
181
+ const fileName = fileData?.data?.name || String(fileId || "");
182
+ const derivedType = deriveTypeFromFilename(fileName);
183
+
184
+ if (!derivedType) {
185
+ throw new Error("Unsupported file type. Allowed: pdf, csv, doc.");
186
+ }
187
+
188
+ const payload = {
189
+ name: document.value.name,
190
+ attachment: document.value.attachment,
191
+ type: derivedType,
192
+ size: fileData?.data?.size ?? fileData?.data?.file_size ?? 0,
441
193
  status: "active",
442
194
  org: orgId,
443
195
  site: siteId,
444
196
  parentId: effectiveParentId.value,
445
- items: rows.value.map((row) => ({
446
- name: row.customName,
447
- attachment: [row.id],
448
- type: deriveTypeFromFilename(row.originalName),
449
- remarks: row.remarks,
450
- expiryDate: toIsoOrNull(row.expiryDate),
451
- })),
452
- });
453
- }
197
+ };
454
198
 
455
- if (isUpdate.value) {
456
- const row = rows.value[0];
457
- await updateById(prop.document._id ?? "", {
458
- name: row.customName,
459
- attachment: [row.id],
460
- type: deriveTypeFromFilename(row.originalName),
461
- remarks: row.remarks,
462
- expiryDate: toIsoOrNull(row.expiryDate),
463
- });
199
+ await add(payload);
464
200
  }
465
201
 
466
- if (isEdit.value) {
467
- // The attachment is resent unchanged on purpose. The update service
468
- // treats a missing `attachment` as "no files", blanking the document's
469
- // file and zeroing its size - so a title-only edit would destroy the
470
- // document it was meant to rename.
471
- await updateById(prop.document._id ?? "", {
472
- name: editTitle.value,
473
- remarks: editRemarks.value,
474
- attachment: existingAttachmentId.value
475
- ? [existingAttachmentId.value]
476
- : [],
477
- });
202
+ if (prop.mode === "edit") {
203
+ const fileId = Array.isArray(document.value.attachment)
204
+ ? document.value.attachment[0]
205
+ : document.value.attachment;
206
+
207
+ let payload: Record<string, any> = {
208
+ name: document.value.name,
209
+ attachment: document.value.attachment,
210
+ };
211
+
212
+ if (fileId) {
213
+ const fileData = await getFileById(fileId);
214
+ const fileName = fileData?.data?.name || String(fileId || "");
215
+ const derivedType = deriveTypeFromFilename(fileName);
216
+ if (derivedType) payload.type = derivedType;
217
+ payload.size = fileData?.data?.size ?? fileData?.data?.file_size ?? 0;
218
+ }
219
+
220
+ await updateById(prop.document._id ?? "", payload);
478
221
  }
479
222
 
480
223
  emit("success");
481
224
  } catch (error: any) {
482
225
  message.value =
483
- error?.response?._data?.message || "Failed to save document.";
226
+ error.response?._data?.message || "Failed to create document";
484
227
  } finally {
485
- saving.value = false;
228
+ disable.value = false;
486
229
  }
487
230
  }
488
231
  </script>
489
-
490
- <style scoped>
491
- .doc-form__drop {
492
- display: flex;
493
- align-items: center;
494
- justify-content: center;
495
- min-height: 120px;
496
- border: 1px dashed var(--border);
497
- border-radius: var(--r-inner);
498
- cursor: pointer;
499
- transition: background-color 0.15s ease, border-color 0.15s ease;
500
- }
501
-
502
- .doc-form__drop:hover,
503
- .doc-form__drop--over {
504
- border-color: var(--text);
505
- background-color: var(--hover);
506
- }
507
-
508
- .doc-form__chip {
509
- display: flex;
510
- align-items: center;
511
- padding: 12px;
512
- border: 1px dashed var(--border);
513
- border-radius: var(--r-inner);
514
- }
515
-
516
- .doc-form__table :deep(th) {
517
- font-size: var(--fs-cell);
518
- font-weight: 500;
519
- white-space: nowrap;
520
- }
521
-
522
- .doc-form__table :deep(td) {
523
- padding-top: 8px;
524
- padding-bottom: 8px;
525
- }
526
- </style>