@7365admin1/layer-common 3.0.5 → 3.0.7
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 +12 -0
- package/components/BulletinBoardForm.vue +423 -333
- package/components/DashboardMain.vue +168 -74
- package/components/InvitationMain.vue +1 -0
- package/components/OnlineFormConfigurationForm.vue +2 -2
- package/components/OnlineFormFill.vue +193 -51
- package/components/VehicleManagement.vue +6 -2
- package/components/VisitorManagement.vue +1 -1
- package/composables/useDashboard.ts +25 -1
- package/composables/useOnlineForm.ts +25 -13
- package/composables/useOnlineFormPages.ts +2 -2
- package/package.json +1 -1
- package/types/dashboard.d.ts +1 -1
|
@@ -1,406 +1,496 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
<v-card
|
|
3
|
+
width="100%"
|
|
4
|
+
:loading="loading.processing"
|
|
5
|
+
:disabled="loading.processing || getAnnouncementByIdPending"
|
|
6
|
+
:key="`form-key-${formKey}`"
|
|
7
|
+
>
|
|
8
|
+
<v-toolbar>
|
|
9
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
10
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
11
|
+
{{ prop.mode }} Announcement
|
|
12
|
+
</span>
|
|
13
|
+
</v-row>
|
|
14
|
+
</v-toolbar>
|
|
15
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-6">
|
|
16
|
+
<v-form
|
|
17
|
+
ref="formRef"
|
|
18
|
+
v-model="validForm"
|
|
19
|
+
:disabled="loading.processing"
|
|
20
|
+
@click="errorMessage = ''"
|
|
21
|
+
>
|
|
22
|
+
<v-row no-gutters>
|
|
23
|
+
<v-col cols="12" class="font-weight-bold text-h6 mb-2"
|
|
24
|
+
>General Information</v-col
|
|
25
|
+
>
|
|
26
|
+
<v-col cols="12">
|
|
27
|
+
<InputLabel title="Recipient(s)" />
|
|
28
|
+
<v-select
|
|
29
|
+
v-model="bulletinForm.recipients"
|
|
30
|
+
:items="recipientList"
|
|
31
|
+
multiple
|
|
32
|
+
item-value="value"
|
|
33
|
+
placeholder="Select recipients"
|
|
34
|
+
>
|
|
35
|
+
<template v-slot:prepend-item>
|
|
36
|
+
<v-list-item
|
|
37
|
+
title="Select All"
|
|
38
|
+
@click="handleSelectAll"
|
|
39
|
+
class=""
|
|
40
|
+
>
|
|
41
|
+
<template v-slot:prepend>
|
|
42
|
+
<v-checkbox-btn
|
|
43
|
+
:color="
|
|
44
|
+
selectedSomeRecipients ? 'indigo-darken-4' : undefined
|
|
45
|
+
"
|
|
46
|
+
:indeterminate="
|
|
47
|
+
selectedSomeRecipients && !selectedAllRecipients
|
|
48
|
+
"
|
|
49
|
+
:model-value="selectedAllRecipients"
|
|
50
|
+
></v-checkbox-btn>
|
|
51
|
+
</template>
|
|
52
|
+
</v-list-item>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<template v-slot:selection="{ item, index }">
|
|
56
|
+
<v-chip :text="item.title" class="py-2">
|
|
57
|
+
<template #append>
|
|
58
|
+
<v-btn
|
|
59
|
+
icon="mdi-close"
|
|
60
|
+
size="small"
|
|
61
|
+
density="compact"
|
|
62
|
+
color="transparent"
|
|
63
|
+
flat
|
|
64
|
+
class="ml-3 text-black"
|
|
65
|
+
@mousedown.stop
|
|
66
|
+
@click.stop="handleRemoveRecipient(item)"
|
|
67
|
+
/>
|
|
68
|
+
</template>
|
|
69
|
+
</v-chip>
|
|
70
|
+
</template>
|
|
71
|
+
</v-select>
|
|
72
|
+
</v-col>
|
|
73
|
+
<v-col cols="12">
|
|
74
|
+
<InputLabel class="text-capitalize" title="Title" required />
|
|
75
|
+
<v-text-field
|
|
76
|
+
v-model.trim="bulletinForm.title"
|
|
77
|
+
density="compact"
|
|
78
|
+
:rules="[requiredRule]"
|
|
79
|
+
/>
|
|
80
|
+
</v-col>
|
|
81
|
+
<v-col cols="12" class="my-4">
|
|
82
|
+
<InputLabel class="text-capitalize" title="Content" required />
|
|
83
|
+
<Editor v-model="bulletinForm.content" :readOnly="false" />
|
|
84
|
+
</v-col>
|
|
85
|
+
|
|
86
|
+
<v-col cols="12">
|
|
87
|
+
<v-checkbox
|
|
88
|
+
v-model="bulletinForm.noExpiration"
|
|
89
|
+
label="No Expiration"
|
|
90
|
+
/>
|
|
91
|
+
</v-col>
|
|
92
|
+
|
|
93
|
+
<v-col cols="12">
|
|
94
|
+
<InputLabel class="text-capitalize" title="Start Date" required />
|
|
95
|
+
<InputDateTimePicker
|
|
96
|
+
v-model:utc="bulletinForm.startDate"
|
|
97
|
+
:rules="[validStartDateRule]"
|
|
98
|
+
/>
|
|
99
|
+
</v-col>
|
|
100
|
+
|
|
101
|
+
<v-col cols="12" v-if="!bulletinForm.noExpiration">
|
|
102
|
+
<InputLabel class="text-capitalize" title="End Date" required />
|
|
103
|
+
<InputDateTimePicker
|
|
104
|
+
v-model:utc="bulletinForm.endDate"
|
|
105
|
+
:rules="[validExpiryDateRule]"
|
|
106
|
+
:min="today"
|
|
107
|
+
/>
|
|
108
|
+
</v-col>
|
|
109
|
+
|
|
110
|
+
<v-col cols="12">
|
|
111
|
+
<v-col cols="12" class="font-weight-bold text-h6"
|
|
112
|
+
>Attachments</v-col
|
|
113
|
+
>
|
|
114
|
+
<v-col cols="12">
|
|
115
|
+
<v-btn
|
|
116
|
+
text="Upload"
|
|
117
|
+
class="text-capitalize"
|
|
118
|
+
color="primary-button"
|
|
119
|
+
variant="outlined"
|
|
120
|
+
min-width="150"
|
|
121
|
+
@click="handleUpload"
|
|
122
|
+
:loading="loading.uploading"
|
|
123
|
+
/>
|
|
124
|
+
</v-col>
|
|
125
|
+
<v-row no-gutters class="px-5">
|
|
126
|
+
<v-col cols="12">Videos ({{ videosArray.length }})</v-col>
|
|
127
|
+
<AttachmentList
|
|
128
|
+
:list="videosArray"
|
|
129
|
+
show-checkbox
|
|
130
|
+
@set-preview="handleSetPreview"
|
|
131
|
+
@remove="handleRemoveFile"
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<v-col cols="12" class="mt-5"
|
|
135
|
+
>Photos ({{ photosArray.length }})</v-col
|
|
136
|
+
>
|
|
137
|
+
<AttachmentList
|
|
138
|
+
:list="photosArray"
|
|
139
|
+
show-checkbox
|
|
140
|
+
@set-preview="handleSetPreview"
|
|
141
|
+
@remove="handleRemoveFile"
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
<v-col cols="12" class="mt-5"
|
|
145
|
+
>Files ({{ filesArray.length }})</v-col
|
|
146
|
+
>
|
|
147
|
+
<AttachmentList
|
|
148
|
+
:list="filesArray"
|
|
149
|
+
show-checkbox
|
|
150
|
+
@set-preview="handleSetPreview"
|
|
151
|
+
@remove="handleRemoveFile"
|
|
152
|
+
/>
|
|
9
153
|
</v-row>
|
|
10
|
-
|
|
11
|
-
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-6">
|
|
12
|
-
<v-form ref="formRef" v-model="validForm" :disabled="loading.processing" @click="errorMessage = ''">
|
|
13
|
-
<v-row no-gutters>
|
|
14
|
-
<v-col cols="12" class="font-weight-bold text-h6 mb-2">General Information</v-col>
|
|
15
|
-
<v-col cols="12">
|
|
16
|
-
<InputLabel title="Recipient(s)" />
|
|
17
|
-
<v-select v-model="bulletinForm.recipients" :items="recipientList" multiple item-value="value"
|
|
18
|
-
placeholder="Select recipients">
|
|
19
|
-
<template v-slot:prepend-item>
|
|
20
|
-
<v-list-item title="Select All" @click="handleSelectAll" class="">
|
|
21
|
-
<template v-slot:prepend>
|
|
22
|
-
<v-checkbox-btn :color="selectedSomeRecipients ? 'indigo-darken-4' : undefined"
|
|
23
|
-
:indeterminate="selectedSomeRecipients && !selectedAllRecipients"
|
|
24
|
-
:model-value="selectedAllRecipients"></v-checkbox-btn>
|
|
25
|
-
</template>
|
|
26
|
-
</v-list-item>
|
|
27
|
-
</template>
|
|
28
|
-
|
|
29
|
-
<template v-slot:selection="{ item, index }">
|
|
30
|
-
<v-chip :text="item.title" class="py-2">
|
|
31
|
-
<template #append>
|
|
32
|
-
<v-btn icon="mdi-close" size="small" density="compact" color="transparent" flat
|
|
33
|
-
class="ml-3 text-black" @mousedown.stop
|
|
34
|
-
@click.stop="handleRemoveRecipient(item)" />
|
|
35
|
-
</template>
|
|
36
|
-
</v-chip>
|
|
37
|
-
</template>
|
|
38
|
-
</v-select>
|
|
39
|
-
</v-col>
|
|
40
|
-
<v-col cols="12">
|
|
41
|
-
<InputLabel class="text-capitalize" title="Title" required />
|
|
42
|
-
<v-text-field v-model.trim="bulletinForm.title" density="compact" :rules="[requiredRule]" />
|
|
43
|
-
</v-col>
|
|
44
|
-
<v-col cols="12" class="my-4">
|
|
45
|
-
<InputLabel class="text-capitalize" title="Content" required />
|
|
46
|
-
<Editor v-model="bulletinForm.content" />
|
|
47
|
-
</v-col>
|
|
48
|
-
|
|
49
|
-
<v-col cols="12">
|
|
50
|
-
<v-checkbox v-model="bulletinForm.noExpiration" label="No Expiration" />
|
|
51
|
-
</v-col>
|
|
52
|
-
|
|
53
|
-
<v-col cols="12">
|
|
54
|
-
<InputLabel class="text-capitalize" title="Start Date" required />
|
|
55
|
-
<InputDateTimePicker v-model:utc="bulletinForm.startDate" :rules="[validStartDateRule]"/>
|
|
56
|
-
</v-col>
|
|
57
|
-
|
|
58
|
-
<v-col cols="12" v-if="!bulletinForm.noExpiration">
|
|
59
|
-
<InputLabel class="text-capitalize" title="End Date" required />
|
|
60
|
-
<InputDateTimePicker v-model:utc="bulletinForm.endDate" :rules="[validExpiryDateRule]" :min="today" />
|
|
61
|
-
</v-col>
|
|
62
|
-
|
|
63
|
-
<v-col cols="12">
|
|
64
|
-
<v-col cols="12" class="font-weight-bold text-h6">Attachments</v-col>
|
|
65
|
-
<v-col cols="12">
|
|
66
|
-
<v-btn text="Upload" class="text-capitalize" color="primary-button" variant="outlined"
|
|
67
|
-
min-width="150" @click="handleUpload" :loading="loading.uploading" />
|
|
68
|
-
</v-col>
|
|
69
|
-
<v-row no-gutters class="px-5">
|
|
70
|
-
<v-col cols="12">Videos ({{ videosArray.length }})</v-col>
|
|
71
|
-
<AttachmentList :list="videosArray" show-checkbox @set-preview="handleSetPreview"
|
|
72
|
-
@remove="handleRemoveFile" />
|
|
73
|
-
|
|
74
|
-
<v-col cols="12" class="mt-5">Photos ({{ photosArray.length }})</v-col>
|
|
75
|
-
<AttachmentList :list="photosArray" show-checkbox @set-preview="handleSetPreview"
|
|
76
|
-
@remove="handleRemoveFile" />
|
|
77
|
-
|
|
78
|
-
<v-col cols="12" class="mt-5">Files ({{ filesArray.length }})</v-col>
|
|
79
|
-
<AttachmentList :list="filesArray" show-checkbox @set-preview="handleSetPreview"
|
|
80
|
-
@remove="handleRemoveFile" />
|
|
81
|
-
</v-row>
|
|
82
|
-
</v-col>
|
|
83
|
-
</v-row>
|
|
84
|
-
</v-form>
|
|
85
|
-
</v-card-text>
|
|
86
|
-
|
|
87
|
-
<v-row no-gutters class="w-100" v-if="errorMessage">
|
|
88
|
-
<p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
|
|
154
|
+
</v-col>
|
|
89
155
|
</v-row>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
156
|
+
</v-form>
|
|
157
|
+
</v-card-text>
|
|
158
|
+
|
|
159
|
+
<v-row no-gutters class="w-100" v-if="errorMessage">
|
|
160
|
+
<p class="text-error w-100 text-center text-subtitle-2">
|
|
161
|
+
{{ errorMessage }}
|
|
162
|
+
</p>
|
|
163
|
+
</v-row>
|
|
164
|
+
<v-toolbar density="compact">
|
|
165
|
+
<v-row no-gutters>
|
|
166
|
+
<v-col cols="6">
|
|
167
|
+
<v-btn
|
|
168
|
+
tile
|
|
169
|
+
block
|
|
170
|
+
variant="text"
|
|
171
|
+
class="text-none"
|
|
172
|
+
size="48"
|
|
173
|
+
@click="close"
|
|
174
|
+
text="Close"
|
|
175
|
+
/>
|
|
176
|
+
</v-col>
|
|
177
|
+
<v-col cols="6">
|
|
178
|
+
<v-btn
|
|
179
|
+
tile
|
|
180
|
+
block
|
|
181
|
+
variant="flat"
|
|
182
|
+
color="primary-button"
|
|
183
|
+
class="text-none"
|
|
184
|
+
size="48"
|
|
185
|
+
:text="prop.mode === 'add' ? 'Add Bulletin' : 'Save'"
|
|
186
|
+
:disabled="loading.processing || !validForm"
|
|
187
|
+
@click="onSubmit"
|
|
188
|
+
/>
|
|
189
|
+
</v-col>
|
|
190
|
+
</v-row>
|
|
191
|
+
</v-toolbar>
|
|
192
|
+
</v-card>
|
|
103
193
|
</template>
|
|
104
194
|
|
|
105
195
|
<script setup lang="ts">
|
|
106
196
|
const today = computed(() => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
})
|
|
197
|
+
const d = new Date();
|
|
198
|
+
const yyyy = d.getFullYear();
|
|
199
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
200
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
201
|
+
console.log("${yyyy}-${mm}-${dd}", `${yyyy}-${mm}-${dd}`);
|
|
202
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
203
|
+
});
|
|
114
204
|
|
|
115
205
|
const prop = defineProps({
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
})
|
|
206
|
+
siteId: {
|
|
207
|
+
type: String,
|
|
208
|
+
required: true,
|
|
209
|
+
},
|
|
210
|
+
mode: {
|
|
211
|
+
type: String as PropType<"edit" | "add">,
|
|
212
|
+
default: "add",
|
|
213
|
+
},
|
|
214
|
+
activeId: {
|
|
215
|
+
type: String,
|
|
216
|
+
default: "",
|
|
217
|
+
},
|
|
218
|
+
});
|
|
129
219
|
|
|
130
|
-
const { requiredRule } = useUtils()
|
|
131
|
-
const { addFile, urlToFile, getFileUrl } = useFile()
|
|
132
|
-
const { add, update, recipientList, getBulletinById } = useBulletin()
|
|
220
|
+
const { requiredRule } = useUtils();
|
|
221
|
+
const { addFile, urlToFile, getFileUrl } = useFile();
|
|
222
|
+
const { add, update, recipientList, getBulletinById } = useBulletin();
|
|
133
223
|
|
|
134
224
|
const bulletinForm = reactive<TCreateAnnouncementPayload>({
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
const emit = defineEmits([
|
|
225
|
+
recipients: [],
|
|
226
|
+
title: "",
|
|
227
|
+
content: "",
|
|
228
|
+
site: prop.siteId,
|
|
229
|
+
noExpiration: false,
|
|
230
|
+
startDate: "",
|
|
231
|
+
endDate: "",
|
|
232
|
+
file: [],
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const emit = defineEmits(["close", "done"]);
|
|
146
236
|
|
|
147
237
|
const validForm = ref(false);
|
|
148
|
-
const formRef = ref<HTMLFormElement>()
|
|
149
|
-
const formKey = ref(0)
|
|
150
|
-
const errorMessage = ref(
|
|
238
|
+
const formRef = ref<HTMLFormElement>();
|
|
239
|
+
const formKey = ref(0);
|
|
240
|
+
const errorMessage = ref("");
|
|
151
241
|
|
|
152
|
-
const rawFileArray = ref<{ file: File
|
|
242
|
+
const rawFileArray = ref<{ file: File; _id: string; preview: boolean }[]>([]);
|
|
153
243
|
|
|
154
244
|
const loading = reactive({
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const photosArray = getByType('image/')
|
|
163
|
-
const videosArray = getByType('video/')
|
|
164
|
-
const filesArray = getByType('other')
|
|
165
|
-
|
|
166
|
-
function getByType(prefix: 'image/' | 'video/' | 'other') {
|
|
167
|
-
return computed(() => {
|
|
168
|
-
return rawFileArray.value.filter(x => {
|
|
169
|
-
const type = x?.file?.type
|
|
170
|
-
if (typeof type !== 'string') return false
|
|
245
|
+
uploading: false,
|
|
246
|
+
processing: false,
|
|
247
|
+
});
|
|
171
248
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
249
|
+
const photosArray = getByType("image/");
|
|
250
|
+
const videosArray = getByType("video/");
|
|
251
|
+
const filesArray = getByType("other");
|
|
252
|
+
|
|
253
|
+
function getByType(prefix: "image/" | "video/" | "other") {
|
|
254
|
+
return computed(() => {
|
|
255
|
+
return rawFileArray.value.filter((x) => {
|
|
256
|
+
const type = x?.file?.type;
|
|
257
|
+
if (typeof type !== "string") return false;
|
|
258
|
+
|
|
259
|
+
if (prefix === "other") {
|
|
260
|
+
return !type.startsWith("image/") && !type.startsWith("video/");
|
|
261
|
+
}
|
|
262
|
+
return type.startsWith(prefix);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
178
265
|
}
|
|
179
266
|
|
|
180
|
-
|
|
181
267
|
const {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
268
|
+
data: getAnnouncementByIdReq,
|
|
269
|
+
refresh: getAnnouncementByIdRefresh,
|
|
270
|
+
pending: getAnnouncementByIdPending,
|
|
271
|
+
error: getAnnouncementByIdError,
|
|
186
272
|
} = await useLazyAsyncData(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
return getBulletinById(id)
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
watch: [() => prop.activeId],
|
|
273
|
+
`get-announcements-${prop.activeId}`,
|
|
274
|
+
() => {
|
|
275
|
+
if (prop.mode === "add") return Promise.resolve(null); // ignore when add mode
|
|
276
|
+
const id = prop?.activeId;
|
|
277
|
+
if (!id) {
|
|
278
|
+
throw new Error("Invalid activeId or mode. Cannot fetch announcement.");
|
|
198
279
|
}
|
|
280
|
+
return getBulletinById(id);
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
watch: [() => prop.activeId],
|
|
284
|
+
}
|
|
199
285
|
);
|
|
200
286
|
|
|
201
|
-
watch(
|
|
287
|
+
watch(
|
|
288
|
+
[getAnnouncementByIdReq, getAnnouncementByIdError],
|
|
289
|
+
async ([newData, newError]) => {
|
|
202
290
|
if (newData) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
})
|
|
223
|
-
|
|
291
|
+
bulletinForm.title = newData?.title;
|
|
292
|
+
bulletinForm.content = newData?.content;
|
|
293
|
+
bulletinForm.noExpiration = newData?.noExpiration;
|
|
294
|
+
bulletinForm.startDate = newData?.startDate;
|
|
295
|
+
bulletinForm.endDate = newData?.endDate;
|
|
296
|
+
bulletinForm.file = newData?.file;
|
|
297
|
+
bulletinForm.recipients = newData?.recipients;
|
|
298
|
+
|
|
299
|
+
// mapped rawFileArray
|
|
300
|
+
|
|
301
|
+
rawFileArray.value = await Promise.all(
|
|
302
|
+
newData?.file?.map(async (item: TAnnouncementFile) => {
|
|
303
|
+
const fileUrl = getFileUrl(item?._id);
|
|
304
|
+
const file = await urlToFile(fileUrl, item?.name);
|
|
305
|
+
return {
|
|
306
|
+
file,
|
|
307
|
+
_id: item?._id,
|
|
308
|
+
preview: item.preview ?? false,
|
|
309
|
+
};
|
|
310
|
+
})
|
|
311
|
+
);
|
|
224
312
|
}
|
|
225
313
|
|
|
226
314
|
if (newError) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
315
|
+
const errMessage = newError?.message;
|
|
316
|
+
console.error("Failed to load announcement:", errMessage);
|
|
317
|
+
errorMessage.value = errMessage || "Cannot fetch announcement" + newError;
|
|
230
318
|
}
|
|
231
|
-
}
|
|
319
|
+
}
|
|
320
|
+
);
|
|
232
321
|
|
|
233
|
-
const allRecipientValues: TAnnouncementRecipients[] = recipientList.map(
|
|
322
|
+
const allRecipientValues: TAnnouncementRecipients[] = recipientList.map(
|
|
323
|
+
(r) => r.value
|
|
324
|
+
);
|
|
234
325
|
|
|
235
326
|
const selectedAllRecipients = computed(() => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
327
|
+
return (
|
|
328
|
+
bulletinForm.recipients.length === allRecipientValues.length &&
|
|
329
|
+
bulletinForm.recipients.every((x) => allRecipientValues.includes(x))
|
|
330
|
+
);
|
|
331
|
+
});
|
|
241
332
|
|
|
242
333
|
const selectedSomeRecipients = computed(() => {
|
|
243
|
-
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
|
|
334
|
+
return bulletinForm.recipients?.length > 0;
|
|
335
|
+
});
|
|
247
336
|
|
|
248
337
|
function handleRemoveRecipient(item: any) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
338
|
+
const recipientVal = item?.value;
|
|
339
|
+
const filtered = bulletinForm.recipients.filter((x) => x !== recipientVal);
|
|
340
|
+
bulletinForm.recipients = filtered;
|
|
252
341
|
}
|
|
253
342
|
|
|
254
343
|
function handleSelectAll() {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
344
|
+
if (selectedAllRecipients.value) {
|
|
345
|
+
bulletinForm.recipients = [];
|
|
346
|
+
} else {
|
|
347
|
+
bulletinForm.recipients = allRecipientValues;
|
|
348
|
+
}
|
|
260
349
|
}
|
|
261
350
|
|
|
262
|
-
|
|
263
351
|
function validStartDateRule(value: string) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
352
|
+
if (!value) {
|
|
353
|
+
return "Start Date is required";
|
|
354
|
+
}
|
|
267
355
|
|
|
268
|
-
|
|
356
|
+
return true;
|
|
269
357
|
}
|
|
270
358
|
|
|
271
359
|
function validExpiryDateRule() {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
360
|
+
const noExpiration = bulletinForm.noExpiration;
|
|
361
|
+
if (noExpiration) return true;
|
|
362
|
+
const startDateISO = bulletinForm.startDate;
|
|
363
|
+
const endDateISO = bulletinForm.endDate;
|
|
364
|
+
if (!endDateISO) {
|
|
365
|
+
return "End Date is required";
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (endDateISO && startDateISO) {
|
|
369
|
+
const expiry = new Date(endDateISO);
|
|
370
|
+
const start = new Date(startDateISO as string);
|
|
371
|
+
return expiry >= start || "End date must be equal or later than start date";
|
|
372
|
+
}
|
|
373
|
+
return true;
|
|
286
374
|
}
|
|
287
375
|
|
|
288
|
-
watch(
|
|
376
|
+
watch(
|
|
377
|
+
() => bulletinForm.noExpiration,
|
|
378
|
+
(newVal) => {
|
|
289
379
|
if (newVal === true) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
bulletinForm.endDate = "";
|
|
382
|
+
}, 100);
|
|
293
383
|
}
|
|
294
|
-
}
|
|
295
|
-
|
|
384
|
+
}
|
|
385
|
+
);
|
|
296
386
|
|
|
297
387
|
async function onSubmit() {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
388
|
+
errorMessage.value = "";
|
|
389
|
+
await formRef.value?.validate();
|
|
390
|
+
if (!validForm.value) {
|
|
391
|
+
errorMessage.value = "Please complete required fields";
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let payload: Partial<TCreateAnnouncementPayload> = {};
|
|
396
|
+
if (prop.mode === "add") {
|
|
397
|
+
payload = { ...bulletinForm };
|
|
398
|
+
} else if (prop.mode === "edit") {
|
|
399
|
+
const { site, ...rest } = bulletinForm;
|
|
400
|
+
payload = rest;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
loading.processing = true;
|
|
405
|
+
if (prop.mode === "add") {
|
|
406
|
+
await add(payload);
|
|
407
|
+
} else if (prop.mode === "edit") {
|
|
408
|
+
const id = prop.activeId;
|
|
409
|
+
if (!id) throw new Error("Missing activeId prop");
|
|
410
|
+
await update(prop.activeId, payload);
|
|
411
|
+
} else {
|
|
412
|
+
throw new Error("Missing mode prop!");
|
|
311
413
|
}
|
|
312
414
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
throw new Error('Missing mode prop!')
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
emit('done')
|
|
326
|
-
|
|
327
|
-
} catch (error: any) {
|
|
328
|
-
const err = error?.data?.message
|
|
329
|
-
errorMessage.value = err || `Failed to ${prop.mode === 'add' ? 'add' : 'update'}. Please try again.`;
|
|
330
|
-
|
|
331
|
-
} finally {
|
|
332
|
-
loading.processing = false
|
|
333
|
-
}
|
|
415
|
+
emit("done");
|
|
416
|
+
} catch (error: any) {
|
|
417
|
+
const err = error?.data?.message;
|
|
418
|
+
errorMessage.value =
|
|
419
|
+
err ||
|
|
420
|
+
`Failed to ${prop.mode === "add" ? "add" : "update"}. Please try again.`;
|
|
421
|
+
} finally {
|
|
422
|
+
loading.processing = false;
|
|
423
|
+
}
|
|
334
424
|
}
|
|
335
425
|
|
|
336
426
|
function close() {
|
|
337
|
-
|
|
427
|
+
emit("close");
|
|
338
428
|
}
|
|
339
429
|
|
|
340
430
|
function handleUpload() {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// bulletinForm.file.push({ name: fileName, preview: false, _id: fileId })
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
catch (err: any) {
|
|
368
|
-
console.error("Upload failed:", err)
|
|
369
|
-
const message = err?.data?.message
|
|
370
|
-
errorMessage.value = "Failed to upload image. Please try again."
|
|
371
|
-
} finally {
|
|
372
|
-
loading.uploading = false
|
|
373
|
-
}
|
|
374
|
-
|
|
431
|
+
const input = document.createElement("input");
|
|
432
|
+
input.type = "file";
|
|
433
|
+
input.multiple = true;
|
|
434
|
+
|
|
435
|
+
input.onchange = async (event: Event) => {
|
|
436
|
+
const target = event.target as HTMLInputElement;
|
|
437
|
+
const filesArray = target.files || [];
|
|
438
|
+
const MAX_SIZE = 25 * 1024 * 1024; // 25MB
|
|
439
|
+
for (const file of filesArray) {
|
|
440
|
+
loading.uploading = true;
|
|
441
|
+
|
|
442
|
+
if (file.size > MAX_SIZE) {
|
|
443
|
+
errorMessage.value = `Skipping File "${file.name}", exceeds 25MB limit.`;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const res = await addFile(file);
|
|
449
|
+
const fileId = res?.id;
|
|
450
|
+
const fileName = file?.name || `file-${fileId}`;
|
|
451
|
+
if (fileId) {
|
|
452
|
+
rawFileArray.value.push({ file, _id: fileId, preview: false });
|
|
453
|
+
// bulletinForm.file.push({ name: fileName, preview: false, _id: fileId })
|
|
375
454
|
}
|
|
455
|
+
} catch (err: any) {
|
|
456
|
+
console.error("Upload failed:", err);
|
|
457
|
+
const message = err?.data?.message;
|
|
458
|
+
errorMessage.value = "Failed to upload image. Please try again.";
|
|
459
|
+
} finally {
|
|
460
|
+
loading.uploading = false;
|
|
461
|
+
}
|
|
376
462
|
}
|
|
377
|
-
|
|
463
|
+
};
|
|
464
|
+
input.click();
|
|
378
465
|
}
|
|
379
466
|
|
|
380
467
|
function handleSetPreview(fileId: string) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
468
|
+
rawFileArray.value.forEach((item) => {
|
|
469
|
+
if (item._id !== fileId) {
|
|
470
|
+
item.preview = false;
|
|
471
|
+
}
|
|
472
|
+
});
|
|
386
473
|
}
|
|
387
474
|
|
|
388
475
|
function handleRemoveFile(fileId: string) {
|
|
389
|
-
|
|
476
|
+
rawFileArray.value = rawFileArray.value.filter(
|
|
477
|
+
(item) => item?._id !== fileId
|
|
478
|
+
);
|
|
390
479
|
}
|
|
391
480
|
|
|
392
|
-
watch(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
481
|
+
watch(
|
|
482
|
+
rawFileArray,
|
|
483
|
+
(newArr) => {
|
|
484
|
+
bulletinForm.file = newArr.map((item) => {
|
|
485
|
+
return {
|
|
486
|
+
_id: item?._id,
|
|
487
|
+
name: item?.file?.name,
|
|
488
|
+
preview: item?.preview,
|
|
489
|
+
};
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
{ deep: true }
|
|
493
|
+
);
|
|
404
494
|
</script>
|
|
405
495
|
|
|
406
|
-
<style scoped></style>
|
|
496
|
+
<style scoped></style>
|