@7365admin1/layer-common 1.11.23 → 1.11.26

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,23 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 1.11.26
4
+
5
+ ### Patch Changes
6
+
7
+ - d418c67: Update builing management image upload
8
+
9
+ ## 1.11.25
10
+
11
+ ### Patch Changes
12
+
13
+ - 949ad2b: Update Layer-common version
14
+
15
+ ## 1.11.24
16
+
17
+ ### Patch Changes
18
+
19
+ - 68d57fd: Update Enhancement in VMS
20
+
3
21
  ## 1.11.23
4
22
 
5
23
  ### Patch Changes
@@ -97,7 +97,7 @@ const props = defineProps({
97
97
  site: { type: String, required: true, default: "" },
98
98
  scheduleAreaId: { type: String, required: true, default: "" },
99
99
  type: { type: String, required: true, default: "cleaner" },
100
- scheduleRoute: { type: String, default: "cleaning-schedule" },
100
+ scheduleRoute: { type: String, default: "pest-schedule" },
101
101
  });
102
102
 
103
103
  const { formatDate, back, debounce } = useUtils();
@@ -0,0 +1,266 @@
1
+ <template>
2
+ <v-col cols="12" v-if="files?.length > 0" class="mb-5">
3
+ <v-row no-gutters>
4
+ <v-col v-if="showTitle" class="font-weight-bold">{{ title }}</v-col>
5
+ <v-col cols="12" class="">
6
+
7
+ <!-- Loading Skeleton -->
8
+ <template v-if="loading && files?.length > 0">
9
+ <v-card v-for="(file, index) in files" :key="index" height="50px" flat border="sm black" class="mb-2">
10
+ <v-skeleton-loader type="list-item" class="mb-2" />
11
+ </v-card>
12
+ </template>
13
+
14
+ <!-- Files Content -->
15
+ <template v-else>
16
+
17
+ <!-- Videos -->
18
+ <template v-if="videosArray.length > 0">
19
+ <div v-if="showFileTypeTitle" class="text-caption text-medium-emphasis mb-1">Videos ({{
20
+ videosArray.length }})</div>
21
+ <v-list density="compact" class="pa-0 rounded-lg border mb-2">
22
+ <template v-for="(item, i) in videosArray" :key="item._id">
23
+ <v-divider v-if="i > 0" />
24
+ <v-list-item :prepend-icon="getFileIcon(item.mimeType)" :title="item.file.name"
25
+ :subtitle="item.mimeType" class="cursor-pointer"
26
+ @click="openMediaViewer(item._id, item.mimeType)">
27
+ <template #prepend>
28
+ <v-icon :color="getFileIconColor(item.mimeType)" class="mr-3">{{
29
+ getFileIcon(item.mimeType) }}</v-icon>
30
+ </template>
31
+ <template #append>
32
+ <v-icon size="16" color="grey-lighten-1">mdi-play-circle-outline</v-icon>
33
+ </template>
34
+ </v-list-item>
35
+ </template>
36
+ </v-list>
37
+ </template>
38
+
39
+ <!-- Photos -->
40
+ <template v-if="photosArray.length > 0">
41
+ <div v-if="showFileTypeTitle" class="text-caption text-medium-emphasis mb-1"
42
+ :class="{ 'mt-3': videosArray.length > 0 }">
43
+ Photos ({{ photosArray.length }})</div>
44
+ <v-list density="compact" class="pa-0 rounded-lg border mb-2">
45
+ <template v-for="(item, i) in photosArray" :key="item._id">
46
+ <v-divider v-if="i > 0" />
47
+ <v-list-item class="cursor-pointer" @click="openMediaViewer(item._id, item.mimeType)">
48
+ <template #prepend>
49
+ <v-avatar size="36" rounded="sm" class="mr-3">
50
+ <v-img :src="getFileUrl(item._id)" cover>
51
+ <template #error>
52
+ <v-icon color="green" size="20">mdi-image</v-icon>
53
+ </template>
54
+ </v-img>
55
+ </v-avatar>
56
+ </template>
57
+ <v-list-item-title class="text-body-2">{{ item.file.name }}</v-list-item-title>
58
+ <v-list-item-subtitle class="text-caption">{{ item.mimeType
59
+ }}</v-list-item-subtitle>
60
+ <template #append>
61
+ <v-icon size="16" color="grey-lighten-1">mdi-eye-outline</v-icon>
62
+ </template>
63
+ </v-list-item>
64
+ </template>
65
+ </v-list>
66
+ </template>
67
+
68
+ <!-- Documents & Other Files -->
69
+ <template v-if="filesArray.length > 0">
70
+ <div v-if="showFileTypeTitle" class="text-caption text-medium-emphasis mb-1"
71
+ :class="{ 'mt-3': videosArray.length > 0 || photosArray.length > 0 }">Files ({{
72
+ filesArray.length }})</div>
73
+ <v-list density="compact" class="pa-0 rounded-lg border mb-2">
74
+ <template v-for="(item, i) in filesArray" :key="item._id">
75
+ <v-divider v-if="i > 0" />
76
+ <v-list-item class="cursor-pointer"
77
+ @click="openDocumentViewer(item._id, item.file.name, item.mimeType)">
78
+ <template #prepend>
79
+ <v-icon :color="getFileIconColor(item.mimeType)" class="mr-3">{{
80
+ getFileIcon(item.mimeType) }}</v-icon>
81
+ </template>
82
+ <v-list-item-title class="text-body-2">{{ item.file.name }}</v-list-item-title>
83
+ <v-list-item-subtitle class="text-caption">{{ item.mimeType
84
+ }}</v-list-item-subtitle>
85
+ <template #append>
86
+ <v-icon size="16" color="grey-lighten-1">mdi-open-in-new</v-icon>
87
+ </template>
88
+ </v-list-item>
89
+ </template>
90
+ </v-list>
91
+ </template>
92
+
93
+ </template>
94
+
95
+ </v-col>
96
+ </v-row>
97
+
98
+ <!-- Media Carousel (images + videos) -->
99
+ <ImageCarousel v-model="showCarousel" :active-file-id="activeFileId" :files="carouselFiles" />
100
+
101
+ <!-- Document Viewer -->
102
+ <DocumentViewer v-model="showDocumentViewer" :file-id="activeFileId" :file-name="activeFileName"
103
+ :mime-type="activeFileMimeType" />
104
+ </v-col>
105
+ </template>
106
+
107
+ <script setup lang="ts">
108
+ const props = defineProps({
109
+ files: {
110
+ type: Array as PropType<{ id: string, name: string }[]>,
111
+ required: true,
112
+ },
113
+ title: {
114
+ type: String,
115
+ default: "Attachments",
116
+ },
117
+ showTitle: {
118
+ type: Boolean,
119
+ default: false,
120
+ },
121
+
122
+ showFileTypeTitle: {
123
+ type: Boolean,
124
+ default: false,
125
+ }
126
+ });
127
+
128
+ const { getFileUrl, urlToFile, getFileById } = useFile();
129
+
130
+ const rawFileArray = ref<{ file: File, _id: string, preview: boolean, mimeType: string }[]>([]);
131
+ const loading = ref(true);
132
+
133
+ const photosArray = getByType('image/');
134
+ const videosArray = getByType('video/');
135
+ const filesArray = getByType('other');
136
+
137
+ /* Viewer state */
138
+ const showCarousel = ref(false);
139
+ const showDocumentViewer = ref(false);
140
+ const activeFileId = ref('');
141
+ const activeFileName = ref('');
142
+ const activeFileMimeType = ref('application/octet-stream');
143
+ const carouselFiles = ref<string[]>([]);
144
+
145
+ function openMediaViewer(id: string, mimeType: string) {
146
+ activeFileId.value = id;
147
+ carouselFiles.value = [
148
+ ...photosArray.value.map(x => x._id),
149
+ ...videosArray.value.map(x => x._id),
150
+ ];
151
+ showCarousel.value = true;
152
+ }
153
+
154
+ function openDocumentViewer(id: string, name: string, mimeType: string) {
155
+ activeFileId.value = id;
156
+ activeFileName.value = name;
157
+ activeFileMimeType.value = mimeType;
158
+ showDocumentViewer.value = true;
159
+ }
160
+
161
+ function getFileIcon(mimeType: string): string {
162
+ if (mimeType.startsWith('image/')) return 'mdi-image';
163
+ if (mimeType.startsWith('video/')) return 'mdi-video';
164
+ if (mimeType === 'application/pdf') return 'mdi-file-pdf-box';
165
+ if (mimeType.includes('word') || mimeType.includes('document')) return 'mdi-file-word-box';
166
+ if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return 'mdi-file-excel-box';
167
+ if (mimeType.includes('powerpoint') || mimeType.includes('presentation')) return 'mdi-file-powerpoint-box';
168
+ if (mimeType === 'text/plain') return 'mdi-file-document-outline';
169
+ if (mimeType.includes('zip') || mimeType.includes('compressed') || mimeType.includes('archive')) return 'mdi-folder-zip-outline';
170
+ return 'mdi-file-outline';
171
+ }
172
+
173
+ function getFileIconColor(mimeType: string): string {
174
+ if (mimeType === 'application/pdf') return 'red';
175
+ if (mimeType.includes('word') || mimeType.includes('document')) return 'blue';
176
+ if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return 'green';
177
+ if (mimeType.includes('powerpoint') || mimeType.includes('presentation')) return 'orange';
178
+ if (mimeType.startsWith('video/')) return 'purple';
179
+ return 'grey';
180
+ }
181
+
182
+ function getByType(prefix: 'image/' | 'video/' | 'other') {
183
+ return computed(() => {
184
+ return rawFileArray.value.filter(x => {
185
+ const type = x?.mimeType || x?.file?.type;
186
+ if (typeof type !== 'string') return false;
187
+
188
+ if (prefix === 'other') {
189
+ return !type.startsWith('image/') && !type.startsWith('video/');
190
+ }
191
+ return type.startsWith(prefix);
192
+ });
193
+ });
194
+ }
195
+
196
+ watchEffect(async () => {
197
+ loading.value = true;
198
+ const processedFiles: typeof rawFileArray.value = [];
199
+
200
+ for (const item of (props.files || [])) {
201
+ // Handle building files format: { id, name }
202
+ if (item.id && item.name && !item.file) {
203
+ let mimeType = 'application/octet-stream';
204
+ try {
205
+ const fileMeta = await getFileById(item.id) as any;
206
+ const apiMimeType = fileMeta?.data?.mimeType || fileMeta?.mimeType;
207
+ // Use API mime type only if it's not generic
208
+ if (apiMimeType && apiMimeType !== 'application/octet-stream') {
209
+ mimeType = apiMimeType;
210
+ } else {
211
+ // Fallback to filename-based detection
212
+ const ext = item.name?.split('.').pop()?.toLowerCase();
213
+ mimeType = inferMimeType(ext);
214
+ }
215
+ } catch {
216
+ // Fallback: infer MIME type from file name
217
+ const ext = item.name?.split('.').pop()?.toLowerCase();
218
+ mimeType = inferMimeType(ext);
219
+ }
220
+ processedFiles.push({
221
+ _id: item.id,
222
+ file: new File([], item.name, { type: mimeType }),
223
+ preview: item.preview ?? false,
224
+ mimeType
225
+ });
226
+ }
227
+ // Handle announcement files format: { file, _id, ... }
228
+ else if (item.file && item._id) {
229
+ processedFiles.push({
230
+ ...item,
231
+ preview: item.preview ?? false,
232
+ mimeType: item.file.type || 'application/octet-stream'
233
+ });
234
+ }
235
+ }
236
+
237
+ rawFileArray.value = processedFiles;
238
+ loading.value = false;
239
+ });
240
+
241
+ function inferMimeType(ext?: string): string {
242
+ if (!ext) return 'application/octet-stream';
243
+ const mimeMap: Record<string, string> = {
244
+ 'pdf': 'application/pdf',
245
+ 'doc': 'application/msword',
246
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
247
+ 'xls': 'application/vnd.ms-excel',
248
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
249
+ 'ppt': 'application/vnd.ms-powerpoint',
250
+ 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
251
+ 'txt': 'text/plain',
252
+ 'jpg': 'image/jpeg',
253
+ 'jpeg': 'image/jpeg',
254
+ 'png': 'image/png',
255
+ 'gif': 'image/gif',
256
+ 'webp': 'image/webp',
257
+ 'mp4': 'video/mp4',
258
+ 'webm': 'video/webm',
259
+ 'ogv': 'video/ogg',
260
+ 'zip': 'application/zip',
261
+ 'rar': 'application/x-rar-compressed',
262
+ '7z': 'application/x-7z-compressed',
263
+ };
264
+ return mimeMap[ext] || 'application/octet-stream';
265
+ }
266
+ </script>
@@ -12,17 +12,9 @@
12
12
  <v-row no-gutters class="px-6 pt-4">
13
13
  <v-col cols="12" class="mt-2">
14
14
  <v-row no-gutters>
15
- <InputLabel
16
- class="text-capitalize font-weight-bold"
17
- title="Name"
18
- required
19
- />
15
+ <InputLabel class="text-capitalize font-weight-bold" title="Name" required />
20
16
  <v-col cols="12">
21
- <v-text-field
22
- v-model="building.name"
23
- density="comfortable"
24
- :rules="[requiredRule]"
25
- ></v-text-field>
17
+ <v-text-field v-model="building.name" density="comfortable" :rules="[requiredRule]"></v-text-field>
26
18
  </v-col>
27
19
  </v-row>
28
20
  </v-col>
@@ -31,51 +23,30 @@
31
23
  <v-row>
32
24
  <v-col cols="6" class="mt-2">
33
25
  <v-row no-gutters>
34
- <InputLabel
35
- class="text-capitalize font-weight-bold"
36
- title="Block"
37
- required
38
- />
26
+ <InputLabel class="text-capitalize font-weight-bold" title="Block" required />
39
27
  <v-col cols="12">
40
- <v-select
41
- v-model.number="building.block"
42
- :items="blocks"
43
- item-title="label"
44
- item-value="value"
45
- density="comfortable"
46
- :disabled="prop.mode === 'edit'"
47
- :rules="[
28
+ <v-select v-model.number="building.block" :items="blocks" item-title="label" item-value="value"
29
+ density="comfortable" :disabled="prop.mode === 'edit'" :rules="[
48
30
  requiredRule,
49
31
  () =>
50
32
  (building.block && building.block > 0) ||
51
33
  'Block is required',
52
- ]"
53
- type="number"
54
- >
55
- <template v-slot:item="{ props: itemProps, item}">
56
- <v-list-item v-bind="itemProps" :disabled="item?.raw?.disabled" ></v-list-item>
57
- </template>
58
-
59
- </v-select>
34
+ ]" type="number">
35
+ <template v-slot:item="{ props: itemProps, item }">
36
+ <v-list-item v-bind="itemProps" :disabled="item?.raw?.disabled"></v-list-item>
37
+ </template>
38
+
39
+ </v-select>
60
40
  </v-col>
61
41
  </v-row>
62
42
  </v-col>
63
43
 
64
44
  <v-col cols="6" class="mt-2">
65
45
  <v-row no-gutters>
66
- <InputLabel
67
- class="text-capitalize font-weight-bold"
68
- title="Levels"
69
- required
70
- />
46
+ <InputLabel class="text-capitalize font-weight-bold" title="Levels" required />
71
47
  <v-col cols="12">
72
- <v-text-field
73
- v-model.number="buildingLevels"
74
- density="comfortable"
75
- :rules="[requiredRule]"
76
- type="number"
77
- @update:model-value="setBuildingLevels()"
78
- ></v-text-field>
48
+ <v-text-field v-model.number="buildingLevels" density="comfortable" :rules="[requiredRule]"
49
+ type="number" @update:model-value="setBuildingLevels()"></v-text-field>
79
50
  </v-col>
80
51
  </v-row>
81
52
  </v-col>
@@ -85,14 +56,8 @@
85
56
  <v-col v-if="buildingLevels" cols="12">
86
57
  <v-row justify="center">
87
58
  <v-col cols="6">
88
- <v-btn
89
- block
90
- color="primary"
91
- variant="text"
92
- class="text-none font-weight-bold"
93
- text="Set level labels"
94
- @click="show = !show"
95
- ></v-btn>
59
+ <v-btn block color="primary" variant="text" class="text-none font-weight-bold" text="Set level labels"
60
+ @click="show = !show"></v-btn>
96
61
  </v-col>
97
62
  </v-row>
98
63
  </v-col>
@@ -100,21 +65,12 @@
100
65
 
101
66
  <v-expand-transition>
102
67
  <v-row v-show="show" no-gutters class="px-6">
103
- <template
104
- v-for="(level, levelIndex) in building.levels"
105
- :key="levelIndex"
106
- >
68
+ <template v-for="(level, levelIndex) in building.levels" :key="levelIndex">
107
69
  <v-col cols="12">
108
70
  <v-row no-gutters>
109
- <InputLabel
110
- class="text-capitalize font-weight-bold"
111
- :title="`Level ${levelIndex + 1}`"
112
- />
71
+ <InputLabel class="text-capitalize font-weight-bold" :title="`Level ${levelIndex + 1}`" />
113
72
  <v-col cols="12">
114
- <v-text-field
115
- v-model.trim="building.levels[levelIndex]"
116
- density="comfortable"
117
- ></v-text-field>
73
+ <v-text-field v-model.trim="building.levels[levelIndex]" density="comfortable"></v-text-field>
118
74
  </v-col>
119
75
  </v-row>
120
76
  </v-col>
@@ -122,10 +78,10 @@
122
78
  </v-row>
123
79
  </v-expand-transition>
124
80
 
125
- <!-- <v-col cols="12" class="px-6">
126
- <InputLabel class="text-capitalize" title="Building Floor plan" />
127
- <InputFileV2 v-model="building.buildingFloorPlan" :multiple="true" :max-length="10" title="Upload Images" />
128
- </v-col> -->
81
+ <v-col cols="12" class="px-6">
82
+ <InputLabel class="text-capitalize" title="Building Floor plan" />
83
+ <InputFileV2 v-model="building.buildingFiles" :multiple="true" accept="*" :max-length="10" title="Upload Files" />
84
+ </v-col>
129
85
 
130
86
  <v-col cols="12" class="mt-2">
131
87
  <v-checkbox v-model="createMore" density="comfortable" hide-details>
@@ -140,9 +96,7 @@
140
96
  <v-col cols="12">
141
97
  <v-row no-gutters>
142
98
  <v-col cols="12" class="text-center">
143
- <span
144
- class="text-none text-subtitle-2 font-weight-medium text-error"
145
- >
99
+ <span class="text-none text-subtitle-2 font-weight-medium text-error">
146
100
  {{ message }}
147
101
  </span>
148
102
  </v-col>
@@ -154,31 +108,14 @@
154
108
  <v-toolbar density="compact">
155
109
  <v-row no-gutters>
156
110
  <v-col cols="6">
157
- <v-btn
158
- tile
159
- block
160
- variant="text"
161
- class="text-none"
162
- size="48"
163
- @click="cancel"
164
- :disabled="disable"
165
- >
111
+ <v-btn tile block variant="text" class="text-none" size="48" @click="cancel" :disabled="disable">
166
112
  Cancel
167
113
  </v-btn>
168
114
  </v-col>
169
115
 
170
116
  <v-col cols="6">
171
- <v-btn
172
- tile
173
- block
174
- variant="flat"
175
- color="black"
176
- class="text-none"
177
- size="48"
178
- :disabled="!validForm || disable"
179
- @click="submit"
180
- :loading="disable"
181
- >
117
+ <v-btn tile block variant="flat" color="black" class="text-none" size="48" :disabled="!validForm || disable"
118
+ @click="submit" :loading="disable">
182
119
  Submit
183
120
  </v-btn>
184
121
  </v-col>
@@ -204,7 +141,7 @@ const prop = defineProps({
204
141
  name: "",
205
142
  block: null,
206
143
  levels: [],
207
- buildingFloorPlan: []
144
+ buildingFiles: []
208
145
  }),
209
146
  },
210
147
  blocks: {
@@ -214,6 +151,7 @@ const prop = defineProps({
214
151
  });
215
152
 
216
153
  const { getSiteById } = useSite();
154
+ const { getFileById } = useFile();
217
155
 
218
156
  const site = ref<TSite | null>(null);
219
157
 
@@ -235,7 +173,7 @@ const blocks = computed(() => {
235
173
  const array = Array.from({ length: site.value?.metadata?.block || 0 }, (_, i) => i + 1);
236
174
 
237
175
  const formattedArray = array.map((num) => {
238
- const isDisabled = prop.blocks.some((block) => block.block === num)
176
+ const isDisabled = prop.blocks.some((block) => block.block === num)
239
177
  return {
240
178
  label: num.toString(),
241
179
  value: num,
@@ -266,15 +204,43 @@ const building = ref<TBuilding>({
266
204
  name: "",
267
205
  block: null,
268
206
  levels: [],
269
- buildingFloorPlan: []
207
+ buildingFiles: []
270
208
  });
271
209
 
210
+ // Normalize buildingFiles: extract IDs for InputFileV2, keep names in a separate map
211
+ const buildingFilesNames = ref<Record<string, string>>({});
212
+
272
213
  building.value.site = prop.site;
273
214
  if (prop.mode === "edit") {
274
215
  building.value = JSON.parse(JSON.stringify(prop.building));
275
216
  buildingLevels.value = building.value.levels.length;
217
+
218
+ const rawFiles = building.value.buildingFiles as any[];
219
+ if (rawFiles?.length && typeof rawFiles[0] === 'object') {
220
+ rawFiles.forEach((f: { id: string; name: string }) => {
221
+ buildingFilesNames.value[f.id] = f.name ?? "";
222
+ });
223
+ building.value.buildingFiles = rawFiles.map((f: { id: string }) => f.id);
224
+ }
276
225
  }
277
226
 
227
+ watch(
228
+ () => building.value.buildingFiles as string[],
229
+ async (ids) => {
230
+ for (const id of ids) {
231
+ if (!buildingFilesNames.value[id]) {
232
+ try {
233
+ const meta = await getFileById(id) as any;
234
+ buildingFilesNames.value[id] = meta?.data?.name ?? meta?.name ?? "";
235
+ } catch {
236
+ buildingFilesNames.value[id] = "";
237
+ }
238
+ }
239
+ }
240
+ },
241
+ { deep: true }
242
+ );
243
+
278
244
  const createMore = ref(false);
279
245
  const disable = ref(false);
280
246
 
@@ -289,7 +255,9 @@ async function submit() {
289
255
 
290
256
  try {
291
257
  if (prop.mode === "add") {
292
- await createBuilding(building.value);
258
+ const buildingPayload = { ...building.value };
259
+ buildingPayload.buildingFiles = (building.value.buildingFiles as string[] || []).map((id) => ({ id, name: buildingFilesNames.value[id] ?? "" }));
260
+ await createBuilding(buildingPayload);
293
261
  }
294
262
 
295
263
  if (prop.mode === "edit") {
@@ -297,7 +265,7 @@ async function submit() {
297
265
  name: building.value.name,
298
266
  block: building.value.block,
299
267
  levels: building.value.levels,
300
- buildingFloorPlan: building.value.buildingFloorPlan
268
+ buildingFiles: (building.value.buildingFiles as string[] || []).map((id) => ({ id, name: buildingFilesNames.value[id] ?? "" }))
301
269
  });
302
270
  }
303
271
 
@@ -28,8 +28,7 @@
28
28
  </v-toolbar>
29
29
 
30
30
  <v-data-table :headers="headers" :items="items" item-value="_id" items-per-page="20" fixed-header
31
- hide-default-footer @click:row="tableRowClickHandler"
32
- style="max-height: calc(100vh - (200px))">
31
+ hide-default-footer @click:row="tableRowClickHandler" style="max-height: calc(100vh - (200px))">
33
32
  <template #item.createdAt="{ value }">
34
33
  {{ new Date(value).toLocaleDateString() }}
35
34
  </template>
@@ -70,14 +69,13 @@
70
69
  <strong>Levels:</strong>
71
70
  {{ selectedBuilding?.levels.length ?? "N/A" }}
72
71
  </v-col>
73
- <!-- <template v-if="selectedBuilding?.buildingFloorPlan.length > 0">
74
- <v-col cols="12">
75
- <strong>Building Floor Plan:</strong>
76
- </v-col>
77
- <v-col cols="12">
78
- <InputFileV2 :model-value="selectedBuilding?.buildingFloorPlan" view-mode />
79
- </v-col>
80
- </template> -->
72
+ <v-col cols="12">
73
+ <strong>Files:</strong>
74
+ <div v-if="selectedBuilding?.buildingFiles?.length" class="">
75
+ <AttachmentsViewer :files="selectedBuilding.buildingFiles" title="" show-file-type-title />
76
+ </div>
77
+ <span v-else> None</span>
78
+ </v-col>
81
79
  </v-row>
82
80
  </v-card-text>
83
81
 
@@ -153,6 +151,7 @@
153
151
  </v-col>
154
152
  </v-row>
155
153
  </v-toolbar>
154
+
156
155
  </v-card>
157
156
  </v-dialog>
158
157
 
@@ -270,7 +269,7 @@ const selectedBuilding = ref<TBuilding>({
270
269
  name: "",
271
270
  block: null,
272
271
  levels: [],
273
- buildingFloorPlan: [],
272
+ buildingFiles: [],
274
273
  });
275
274
 
276
275
  function tableRowClickHandler(_: any, data: any) {
@@ -343,7 +342,7 @@ function setBuilding({
343
342
  }
344
343
  </script>
345
344
  <style scoped>
346
- .v-data-table :deep(thead th) {
345
+ .v-data-table :deep(thead th) {
347
346
  font-weight: bold !important;
348
347
  }
349
348
  </style>
@@ -76,6 +76,15 @@
76
76
  {{ value || "No Status" }}
77
77
  </v-chip>
78
78
  </template>
79
+ <template #item.completedAt="{ value }">
80
+ {{
81
+ formatDate
82
+ ? formatDate(value)
83
+ : value
84
+ ? new Date(value).toLocaleString()
85
+ : ""
86
+ }}
87
+ </template>
79
88
  <template #item.download="{ item }">
80
89
  <v-btn
81
90
  v-if="canDownloadSchedule"
@@ -136,7 +145,7 @@ const headers = [
136
145
  { title: "Created Date", value: "createdAt" },
137
146
  { title: "Close in", value: "closeIn" },
138
147
  { title: "Status", value: "status" },
139
- { title: "Completion Date", value: "completionDate" },
148
+ { title: "Completion Date", value: "completedAt" },
140
149
  { title: "", value: "download" },
141
150
  ];
142
151
  const remainingTime = ref({} as Record<string, string>);