@iservice365/layer-common 1.0.9 → 1.0.11

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.
@@ -1,14 +1,14 @@
1
1
  <template>
2
- <div>
3
- <v-text-field ref="dateTimePickerRef" :model-value="dateTimeFormattedReadOnly" @click="openDatePicker" placeholder="MM/DD/YYYY, HH:MM AM/PM" :rules="rules">
2
+ <div class="d-flex flex-column">
3
+ <v-text-field v-bind="$attrs" ref="dateTimePickerRef" :model-value="dateTimeFormattedReadOnly"
4
+ placeholder="MM/DD/YYYY, HH:MM AM/PM" :rules="rules" style="z-index: 10" @click="openDatePicker">
4
5
  <template #append-inner>
5
6
  <v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
6
7
  </template>
7
8
  </v-text-field>
8
9
  <div class="w-100 d-flex align-end ga-3 hidden-input">
9
- <input ref="dateInput" type="datetime-local" v-model="dateTime" />
10
+ <input ref="dateInput" type="datetime-local" v-model="dateTime" />
10
11
  </div>
11
-
12
12
  </div>
13
13
  </template>
14
14
 
@@ -19,30 +19,37 @@ const prop = defineProps({
19
19
  rules: {
20
20
  type: Array as PropType<Array<any>>,
21
21
  default: () => []
22
- }
22
+ },
23
23
  })
24
24
 
25
- const dateTime = defineModel<string | null>({ default: null })
25
+ const { formatDateISO8601 } = useUtils()
26
+ const dateTime = defineModel<string | null>({ default: null }) //2025-10-10T13:09 format
27
+ const dateTimeUTC = defineModel<string | null>('utc', { default: null }) // UTC format
26
28
 
27
29
  const dateTimeFormattedReadOnly = ref<string | null>(null)
28
30
 
29
31
 
30
32
 
33
+
31
34
  const dateInput = ref<HTMLInputElement | null>(null)
32
35
  const dateTimePickerRef = ref<HTMLInputElement | null>(null)
33
36
 
37
+ const isInitialLoad = ref(true)
38
+
34
39
  function openDatePicker() {
35
- dateInput.value?.showPicker()
40
+ setTimeout(() => {
41
+ dateInput.value?.showPicker?.()
42
+ }, 0)
36
43
  }
37
44
 
38
- function validate(){
45
+ function validate() {
39
46
  (dateTimePickerRef.value as any)?.validate()
40
47
  }
41
48
 
49
+ function convertToReadableFormat(dateStr: string): string {
42
50
 
43
- watch(dateTime, (dateVal) => {
44
- if (!dateVal) return dateTimeFormattedReadOnly.value = null
45
- const date = new Date(dateVal)
51
+ if (!dateStr) return "";
52
+ const date = new Date(dateStr)
46
53
  const options: Intl.DateTimeFormatOptions = {
47
54
  year: 'numeric',
48
55
  month: '2-digit',
@@ -52,9 +59,57 @@ watch(dateTime, (dateVal) => {
52
59
  hour12: true
53
60
  }
54
61
  const formatted = date.toLocaleString('en-US', options)
55
- dateTimeFormattedReadOnly.value = formatted
56
- })
62
+ return formatted
63
+ }
64
+
65
+ function handleInitialDate(){
66
+ const dateDefault = dateTime.value
67
+ const dateUTC = dateTimeUTC.value
68
+ if(dateDefault){
69
+ dateTimeFormattedReadOnly.value = convertToReadableFormat(dateDefault)
70
+ const localDate = new Date(dateDefault)
71
+ dateTimeUTC.value = localDate.toISOString()
72
+ } else if (dateUTC){
73
+ dateTimeFormattedReadOnly.value = convertToReadableFormat(dateUTC)
74
+ const localDate = new Date(dateUTC)
75
+ dateTime.value = formatDateISO8601(localDate)
76
+ } else {
77
+ dateTimeFormattedReadOnly.value = null
78
+ }
79
+ }
80
+
57
81
 
82
+ watch(dateTime, (dateVal) => {
83
+ if (isInitialLoad.value) return // ignore the first run
84
+ if (!dateVal) {
85
+ dateTimeFormattedReadOnly.value = null;
86
+ dateTimeUTC.value = null
87
+ return
88
+ }
89
+
90
+ dateTimeFormattedReadOnly.value = convertToReadableFormat(dateVal)
91
+ const localDate = new Date(dateVal)
92
+ dateTimeUTC.value = localDate.toISOString()
93
+
94
+ }, { immediate: false })
95
+
96
+
97
+ onMounted(async () => {
98
+
99
+ handleInitialDate()
100
+ await nextTick()
101
+ isInitialLoad.value = false
102
+
103
+ // Wait until Vuetify renders its internal input
104
+ const nativeInput = dateTimePickerRef.value?.$el?.querySelector('input')
105
+ if (nativeInput) {
106
+ nativeInput.addEventListener('click', (e: MouseEvent) => {
107
+ e.stopPropagation()
108
+ openDatePicker()
109
+ })
110
+ }
111
+
112
+ })
58
113
 
59
114
 
60
115
  defineExpose({
@@ -66,6 +121,6 @@ defineExpose({
66
121
  .hidden-input {
67
122
  opacity: 0;
68
123
  height: 0;
69
- width: 0;
124
+ width: 1px;
70
125
  }
71
126
  </style>
@@ -1,7 +1,15 @@
1
1
  <template>
2
2
  <div>
3
3
  <v-row class="mb-4" align="center" no-gutters>
4
- <v-col cols="10" class="pr-2">
4
+ <v-col
5
+ cols="10"
6
+ class="pr-2"
7
+ v-if="
8
+ props.createdFrom === 'feedback' && props.attachments.length > 0
9
+ ? false
10
+ : true
11
+ "
12
+ >
5
13
  <div
6
14
  class="d-flex align-center justify-center pa-4 rounded-lg border-dashed border border-grey"
7
15
  @dragover.prevent
@@ -37,7 +45,15 @@
37
45
  </div>
38
46
  </v-col>
39
47
 
40
- <v-col cols="2" class="d-flex justify-center">
48
+ <v-col
49
+ cols="2"
50
+ class="d-flex justify-center"
51
+ v-if="
52
+ props.createdFrom === 'feedback' && props.attachments.length > 0
53
+ ? false
54
+ : true
55
+ "
56
+ >
41
57
  <v-btn
42
58
  color="primary-button"
43
59
  min-width="55"
@@ -90,7 +106,15 @@
90
106
  {{ getDisplayName(file) }}
91
107
  </div>
92
108
 
93
- <v-icon size="small" @click.stop="$emit('delete', file)">
109
+ <v-icon
110
+ size="small"
111
+ @click.stop="$emit('delete', file)"
112
+ v-if="
113
+ props.createdFrom === 'feedback' && props.attachments.length > 0
114
+ ? false
115
+ : true
116
+ "
117
+ >
94
118
  mdi-trash-can-outline
95
119
  </v-icon>
96
120
  </v-col>
@@ -104,6 +128,7 @@ const props = defineProps<{
104
128
  attachments: string[];
105
129
  erroredImages?: string[];
106
130
  maxFiles?: number;
131
+ createdFrom: string;
107
132
  }>();
108
133
 
109
134
  const emit = defineEmits<{
@@ -179,7 +204,7 @@ function getThumbnail(fileUrl: string): string {
179
204
  // if (fileUrl.match(/\.(xls|xlsx)$/i))
180
205
  // return "/images/file-thumbnails/excel.png";
181
206
  // return "/images/file-thumbnails/file.png";
182
- return `/api/public/${fileUrl}`
207
+ return `/api/public/${fileUrl}`;
183
208
  }
184
209
 
185
210
  // Modified to try to display the friendly name
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <v-row no-gutters class="w-100 pb-5" @click="resetErrorMessage">
3
+ <v-file-upload v-model="uploadFiles" density="compact" @update:model-value="handleUpdateValue"
4
+ :loading="processing" :disabled="processing" :height="height" title="Upload Images" accept="image/*"
5
+ name="upload_images" class="text-caption w-100" clearable :multiple="multiple">
6
+ <template v-slot:item="{ props: itemProps, file }">
7
+ <v-file-upload-item v-bind="itemProps" lines="one" nav>
8
+ <template v-slot:prepend>
9
+ <v-avatar size="32" rounded></v-avatar>
10
+ </template>
11
+
12
+ <template v-slot:clear="{ props: clearProps }">
13
+ <v-btn color="primary" @click="handleRemove(file)"></v-btn>
14
+ </template>
15
+ </v-file-upload-item>
16
+ </template>
17
+ </v-file-upload>
18
+ <v-row no-gutters class="w-100" v-if="errorMessage">
19
+ <p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
20
+ </v-row>
21
+ </v-row>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+
26
+ const prop = defineProps({
27
+ height: {
28
+ type: Number || String,
29
+ default: 68
30
+ },
31
+ multiple: {
32
+ type: Boolean,
33
+ default: false
34
+ },
35
+ maxLength: {
36
+ type: Number,
37
+ default: 10
38
+ }
39
+
40
+
41
+ })
42
+
43
+ const { addFile, deleteFile, getFileUrl, urlToFile } = useFile()
44
+
45
+ const uploadFiles = ref<File[]>([])
46
+ const filesCollection = defineModel<{ file: File, id: string }[]>({required: true, default: []}) // files collection array
47
+ const showCameraDialog = ref(false)
48
+ const errorMessage = ref('');
49
+ const processing = ref(false)
50
+
51
+ const video = ref<HTMLVideoElement>()
52
+ const canvas = ref<HTMLCanvasElement>()
53
+ const cameraFacingMode = ref('environment')
54
+
55
+
56
+ async function handleRemove(removedFile: File) {
57
+ const fileKey = (f: File) => `${f.name}_${f.size}_${f.lastModified}`
58
+ const arr = filesCollection.value
59
+ const fileId = arr.find(item => fileKey(item.file) === fileKey(removedFile))?.id
60
+
61
+ uploadFiles.value = uploadFiles.value.filter(f => fileKey(f) !== fileKey(removedFile))
62
+ filesCollection.value = arr.filter(item => item.id !== fileId)
63
+ }
64
+
65
+ async function handleUpdateValue(value: File[]) {
66
+ await nextTick()
67
+ const max = prop.maxLength
68
+ const existingLength = filesCollection.value.length
69
+ errorMessage.value = ''
70
+ if ((existingLength + value.length) > max) {
71
+ value = value.slice(0, (max - existingLength))
72
+ errorMessage.value = `Max value of allowed image is ${max}`
73
+ }
74
+ uploadFiles.value = []
75
+ processing.value = true
76
+ // Determine which files are newly added or removed
77
+ const fileKey = (f: File) => `${f.name}_${f.size}_${f.lastModified}`
78
+
79
+ const arr = filesCollection.value
80
+ const collectionKeys = arr.map(x => fileKey(x.file))
81
+ const addedFiles = value.filter(f => !collectionKeys.includes(fileKey(f)))
82
+
83
+ // Upload new files
84
+ processing.value = true
85
+ for (const file of addedFiles) {
86
+ try {
87
+ const res = await addFile(file) // expected to return { id, url } or similar
88
+ if (res?.id) {
89
+ filesCollection.value.push({ file, id: res?.id })
90
+ }
91
+ } catch (err) {
92
+ console.error("Upload failed", err)
93
+ errorMessage.value = `Failed to upload ${file.name}`
94
+ } finally {
95
+ processing.value = false
96
+ uploadFiles.value = filesCollection.value.map(x => x.file)
97
+ }
98
+ }
99
+
100
+ }
101
+
102
+ function resetErrorMessage(){
103
+ errorMessage.value = '';
104
+
105
+ }
106
+
107
+ onMounted(() => {
108
+ filesCollection.value = []
109
+ })
110
+ </script>
111
+
112
+ <style scoped>
113
+ * :deep(.v-file-upload-title) {
114
+ font-size: 1rem;
115
+ font-weight: 500;
116
+ }
117
+
118
+ * :deep(.v-file-upload-items) {
119
+ min-width: 100%;
120
+ }
121
+ </style>
@@ -48,14 +48,13 @@
48
48
  :items="roles"
49
49
  item-title="name"
50
50
  item-value="_id"
51
- :rules="[requiredRule]"
51
+ :rules="[requiredRule]"
52
52
  density="comfortable"
53
53
  ></v-autocomplete>
54
54
  </v-col>
55
55
  </v-row>
56
56
  </v-col>
57
57
 
58
-
59
58
  <v-col v-if="hasSite" cols="12">
60
59
  <v-row no-gutters>
61
60
  <InputLabel class="text-capitalize" title="Site" />
@@ -176,13 +175,13 @@ const props = defineProps({
176
175
  const emit = defineEmits(["cancel", "success", "success:create-more"]);
177
176
 
178
177
  const validForm = ref(false);
179
- const form = ref<HTMLFormElement | null>(null)
178
+ const form = ref<HTMLFormElement | null>(null);
180
179
  const app = computed(() => useRuntimeConfig().public.APP ?? "");
181
180
 
182
181
  const loading = reactive({
183
182
  submittingForm: false,
184
- verifyingEmail: false
185
- })
183
+ verifyingEmail: false,
184
+ });
186
185
 
187
186
  const invite = ref<Record<string, any>>({
188
187
  email: "",
@@ -203,27 +202,36 @@ if (props.mode === "edit") {
203
202
  }
204
203
 
205
204
  const { natureOfBusiness } = useLocal();
205
+ const { orgNature } = useLocalSetup();
206
206
 
207
207
  const apps = computed(() => {
208
208
  const items = [];
209
209
  items.unshift({ title: "Organization", value: "organization" });
210
210
 
211
- if (props.app === "security_agency") {
212
- items.push({ title: "Security Agency", value: "security_agency" });
211
+ const _org = "security_agency";
212
+
213
+ if (props.app === _org || orgNature.value === _org) {
214
+ items.push({ title: "Security Agency", value: _org });
213
215
  }
214
216
 
215
- if (props.app === "cleaning_agency") {
216
- items.push({ title: "Cleaning Agency", value: "cleaning_agency" });
217
+ const _cleaning = "cleaning_services";
218
+
219
+ if (props.app === _cleaning || orgNature.value === _cleaning) {
220
+ items.push({ title: "Cleaning Services", value: _cleaning });
217
221
  }
218
222
 
219
- if (props.app === "property_manager") {
220
- items.push({ title: "Property Manager", value: "property_manager" });
223
+ const _property = "property_management_agency";
224
+
225
+ if (props.app === _property || orgNature.value === _property) {
226
+ items.push({ title: "Property Management Agency", value: _property });
221
227
  }
222
228
 
223
- if (props.app === "mechanical_electrical_services") {
229
+ const _mechanical = "mechanical_electrical_services";
230
+
231
+ if (props.app === _mechanical || orgNature.value === _mechanical) {
224
232
  items.push({
225
233
  title: "Mechanical & Electrical Services",
226
- value: "mechanical_electrical_services",
234
+ value: _mechanical,
227
235
  });
228
236
  }
229
237
 
@@ -239,9 +247,8 @@ const sites = ref<Array<Record<string, any>>>([]);
239
247
  const { getAll: getAllCustomerSite } = useCustomerSite();
240
248
 
241
249
  const { data: siteData, refresh: refreshSiteData } = await useLazyAsyncData(
242
- "get-sites-by-org",
243
- async () => await getAllCustomerSite({ org: props.org, limit: 50 }),
244
- { }
250
+ "get-sites-by-org-" + props.org,
251
+ async () => await getAllCustomerSite({ org: props.org, limit: 50 })
245
252
  );
246
253
 
247
254
  watchEffect(() => {
@@ -275,13 +282,12 @@ watchEffect(() => {
275
282
  }
276
283
  });
277
284
 
278
- function handleUpdateApp(value: string){
285
+ function handleUpdateApp(value: string) {
279
286
  invite.value.role = "";
280
287
  invite.value.site = "";
281
288
  refreshRoles();
282
289
  }
283
290
 
284
-
285
291
  const createMore = ref(false);
286
292
  const disable = ref(false);
287
293
 
@@ -296,14 +302,13 @@ function resetInvite() {
296
302
  message.value = "";
297
303
  }
298
304
 
299
- function handleUpdateSite(siteId: string){
300
- const obj = sites.value.find( x => x?.value === siteId)
301
- invite.value.siteName = obj?.title || ""
305
+ function handleUpdateSite(siteId: string) {
306
+ const obj = sites.value.find((x) => x?.value === siteId);
307
+ invite.value.siteName = obj?.title || "";
302
308
  }
303
309
 
304
310
  const { inviteUser } = useUser();
305
311
 
306
-
307
312
  async function submit() {
308
313
  loading.submittingForm = true;
309
314
  try {
@@ -1,86 +1,39 @@
1
1
  <template>
2
2
  <v-app-bar scroll-behavior="elevate" scroll-threshold="200">
3
- <v-app-bar-nav-icon
4
- v-if="!props.hideNavIcon"
5
- @click="drawer = !drawer"
6
- ></v-app-bar-nav-icon>
3
+ <v-app-bar-nav-icon v-if="!props.hideNavIcon" @click="drawer = !drawer"></v-app-bar-nav-icon>
7
4
 
8
5
  <template #append>
9
- <v-btn
10
- icon="mdi-theme-light-dark"
11
- variant="text"
12
- class="mx-2"
13
- @click="toggleTheme"
14
- />
6
+ <v-btn icon="mdi-theme-light-dark" variant="text" class="mx-2" @click="toggleTheme" />
15
7
 
16
8
  <v-menu offset="10px" :close-on-content-click="false">
17
9
  <template #activator="{ props }">
18
10
  <v-btn fab variant="text" icon v-bind="props" class="mx-2">
19
- <v-avatar color="surface-variant" size="42">
20
- <v-img
21
- v-if="currentUser?.profile"
22
- :src="profile"
23
- width="42"
24
- height="42"
25
- />
26
-
27
- <span v-else class="text-h5">{{ getNameInitials(name) }}</span>
28
- </v-avatar>
11
+ <AvatarMain :image-src="currentUser?.profile" :name="name" />
29
12
  </v-btn>
30
13
  </template>
31
14
 
32
- <v-card
33
- width="350"
34
- max-height="600px"
35
- elevation="2"
36
- rounded="xl"
37
- class="pa-4"
38
- >
15
+ <v-card width="350" max-height="600px" elevation="2" rounded="xl" class="pa-4">
39
16
  <v-row no-gutters>
40
17
  <v-col cols="12">
41
18
  <v-row no-gutters justify="center">
42
- <v-avatar color="surface-variant" size="75">
43
- <v-img
44
- v-if="currentUser?.profile"
45
- :src="profile"
46
- width="75"
47
- height="75"
48
- />
49
-
50
- <span v-else class="text-h5">{{
51
- getNameInitials(name)
52
- }}</span>
53
- </v-avatar>
19
+ <AvatarMain :image-src="currentUser?.profile" :name="name" />
54
20
  </v-row>
55
21
  </v-col>
56
22
 
57
23
  <v-col cols="12" class="text-center mt-2 mb-4">
58
- {{ currentUser?.firstName }} {{ currentUser?.lastName }}
24
+ {{ name }}
59
25
  </v-col>
60
26
 
61
27
  <v-col cols="12" class="mb-3">
62
- <v-btn
63
- v-if="APP_NAME.toLowerCase() !== 'account'"
64
- block
65
- rounded="xl"
66
- variant="tonal"
67
- size="x-large"
68
- class="text-none text-subtitle-1 font-weight-regular"
69
- @click="redirect(APP_ACCOUNT, 'home')"
70
- >
28
+ <v-btn v-if="APP_NAME.toLowerCase() !== 'account'" block rounded="xl" variant="tonal" size="x-large"
29
+ class="text-none text-subtitle-1 font-weight-regular" @click="redirect(APP_ACCOUNT, 'home')">
71
30
  Manage Account
72
31
  </v-btn>
73
32
  </v-col>
74
33
 
75
34
  <v-col cols="12">
76
- <v-btn
77
- block
78
- rounded="xl"
79
- variant="tonal"
80
- size="x-large"
81
- class="text-none text-subtitle-1 font-weight-regular"
82
- @click="logout()"
83
- >
35
+ <v-btn block rounded="xl" variant="tonal" size="x-large"
36
+ class="text-none text-subtitle-1 font-weight-regular" @click="logout()">
84
37
  Logout
85
38
  </v-btn>
86
39
  </v-col>
@@ -133,17 +86,20 @@ function logout() {
133
86
  }
134
87
 
135
88
  const name = computed(() => {
136
- let name = "";
137
- if (currentUser.value?.firstName) {
138
- name = currentUser.value.firstName;
139
- }
89
+ const user = currentUser.value;
90
+ if(!user) return "";
140
91
 
141
- if (currentUser.value?.lastName) {
142
- name += ` ${currentUser.value.lastName}`;
143
- }
92
+ const first = user?.firstName?.trim() || ""
93
+ const last = user?.lastName?.trim() || ""
94
+ const full = [first, last].filter(Boolean).join(" ")
95
+
96
+ if(full) return full;
97
+
98
+ const alternative = user?.name?.trim();
99
+ return alternative || ""
100
+
101
+ })
144
102
 
145
- return name;
146
- });
147
103
 
148
104
  const { getNameInitials } = useUtils();
149
105
  </script>
@@ -16,7 +16,7 @@
16
16
  <v-col cols="12">
17
17
  <v-card width="100%" variant="outlined" border="thin" rounded="lg" :loading="loading">
18
18
  <!-- Toolbar -->
19
- <v-toolbar density="compact" color="grey-lighten-4">
19
+ <v-toolbar density="compact" color="grey-lighten-4" :extension-height="extensionHeight">
20
20
  <template #prepend>
21
21
  <v-btn fab icon density="comfortable" @click="emits('refresh')">
22
22
  <v-icon>mdi-refresh</v-icon>
@@ -34,18 +34,20 @@
34
34
  </template>
35
35
 
36
36
  <template v-if="$slots.extension" #extension>
37
- <slot name="extension" />
37
+ <slot name="extension"/>
38
38
  </template>
39
39
  </v-toolbar>
40
40
 
41
+
41
42
  <!-- Data Table -->
42
43
  <v-data-table :headers="headers" :items="items" :item-value="itemValue" :items-per-page="itemsPerPage"
43
- fixed-header hide-default-footer hide-default-header
44
+ fixed-header hide-default-footer :hide-default-header="!showHeader"
44
45
  @click:row="(_: any, data: any) => emits('row-click', data)" style="max-height: calc(100vh - (200px))">
45
46
  <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
46
47
  <slot :name="slotName" v-bind="slotProps" />
47
48
  </template>
48
49
  </v-data-table>
50
+ <slot name="footer"/>
49
51
  </v-card>
50
52
  </v-col>
51
53
  </v-row>
@@ -93,6 +95,14 @@ const props = defineProps({
93
95
  type: String,
94
96
  default: "-- - -- of --",
95
97
  },
98
+ showHeader: {
99
+ type: Boolean,
100
+ default: false
101
+ },
102
+ extensionHeight: {
103
+ type: Number,
104
+ default: 50
105
+ }
96
106
  });
97
107
 
98
108
  const emits = defineEmits(["create", "refresh", "update:page", "row-click"]);
@@ -105,3 +115,5 @@ watch(
105
115
  }
106
116
  );
107
117
  </script>
118
+
119
+ <style scoped></style>
@@ -10,6 +10,7 @@
10
10
  :attachments="displayedAttachments"
11
11
  :errored-images="localErroredImages"
12
12
  :max-files="maxFiles"
13
+ :created-from="props.createdFrom"
13
14
  @add="handleFileAdded"
14
15
  @delete="deleteFile"
15
16
  @errored="onImageError"
@@ -22,6 +23,11 @@
22
23
  v-model="localWorkOrder.subject"
23
24
  class="mb-1"
24
25
  dense
26
+ :readonly="
27
+ props.createdFrom === 'feedback' && localWorkOrder.subject !== ''
28
+ ? true
29
+ : false
30
+ "
25
31
  />
26
32
 
27
33
  <v-autocomplete
@@ -34,7 +40,16 @@
34
40
  variant="outlined"
35
41
  density="compact"
36
42
  class="mb-2"
37
- clearable
43
+ :clearable="
44
+ props.createdFrom === 'feedback' && localWorkOrder.category !== ''
45
+ ? false
46
+ : true
47
+ "
48
+ :readonly="
49
+ props.createdFrom === 'feedback' && localWorkOrder.category !== ''
50
+ ? true
51
+ : false
52
+ "
38
53
  />
39
54
 
40
55
  <v-checkbox
@@ -86,6 +101,11 @@
86
101
  v-model="localWorkOrder.location"
87
102
  class="mt-1"
88
103
  dense
104
+ :readonly="
105
+ props.createdFrom === 'feedback' && localWorkOrder.location !== ''
106
+ ? true
107
+ : false
108
+ "
89
109
  />
90
110
 
91
111
  <v-textarea
@@ -117,6 +137,7 @@
117
137
  <script setup lang="ts">
118
138
  const props = defineProps<{
119
139
  modelValue: boolean;
140
+ createdFrom: string;
120
141
  workOrder: TWorkOrderCreate & { specificLocation?: string };
121
142
  isEditMode: boolean;
122
143
  loading: boolean;