@eeplatform/nuxt-layer-common 1.2.11 → 1.3.0

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.
@@ -0,0 +1,295 @@
1
+ <template>
2
+ <div class="year-view" :style="{ height: calendarHeight }">
3
+ <div class="year-grid">
4
+ <div
5
+ v-for="month in yearMonths"
6
+ :key="month.month"
7
+ class="mini-month"
8
+ @click="onMonthClick(month)"
9
+ >
10
+ <div class="mini-month-header">
11
+ {{ month.name }}
12
+ </div>
13
+ <div class="mini-month-grid">
14
+ <!-- Weekday headers for mini calendar -->
15
+ <div class="mini-weekday-header">
16
+ <div
17
+ v-for="day in ['S', 'M', 'T', 'W', 'T', 'F', 'S']"
18
+ :key="day"
19
+ class="mini-weekday"
20
+ >
21
+ {{ day }}
22
+ </div>
23
+ </div>
24
+ <!-- Calendar weeks -->
25
+ <div
26
+ v-for="(week, weekIndex) in month.weeks"
27
+ :key="weekIndex"
28
+ class="mini-week"
29
+ >
30
+ <div
31
+ v-for="(day, dayIndex) in week"
32
+ :key="dayIndex"
33
+ class="mini-day"
34
+ :class="{
35
+ 'has-events': day.hasEvents,
36
+ today: day.isToday,
37
+ 'other-month': day.isOtherMonth,
38
+ 'current-month': !day.isOtherMonth && day.dayNumber,
39
+ }"
40
+ >
41
+ {{ day.dayNumber }}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup>
51
+ import { computed } from "vue";
52
+
53
+ const props = defineProps({
54
+ current: {
55
+ type: Date,
56
+ required: true,
57
+ },
58
+ events: {
59
+ type: Array,
60
+ default: () => [],
61
+ },
62
+ calendarHeight: {
63
+ type: String,
64
+ default: "calc(100vh - 100px)",
65
+ },
66
+ });
67
+
68
+ const emit = defineEmits(["monthClick"]);
69
+
70
+ // Helper functions for event calculations
71
+ const isSameDay = (date1, date2) => {
72
+ return date1.toDateString() === date2.toDateString();
73
+ };
74
+
75
+ const isDateInRange = (date, startDate, endDate) => {
76
+ const checkDate = new Date(date);
77
+ checkDate.setHours(0, 0, 0, 0);
78
+ const start = new Date(startDate);
79
+ start.setHours(0, 0, 0, 0);
80
+ const end = new Date(endDate);
81
+ end.setHours(0, 0, 0, 0);
82
+
83
+ return checkDate >= start && checkDate <= end;
84
+ };
85
+
86
+ const getEventsForDate = (date) => {
87
+ return props.events.filter((event) =>
88
+ isDateInRange(date, event.startDate, event.endDate)
89
+ );
90
+ };
91
+
92
+ // Year view computed properties
93
+ const yearMonths = computed(() => {
94
+ const months = [];
95
+ const year = props.current.getFullYear();
96
+
97
+ for (let month = 0; month < 12; month++) {
98
+ const monthData = {
99
+ month,
100
+ name: new Date(year, month, 1).toLocaleDateString("en-US", {
101
+ month: "long",
102
+ }),
103
+ weeks: [],
104
+ };
105
+
106
+ const firstDay = new Date(year, month, 1).getDay();
107
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
108
+ const prevMonth = month === 0 ? 11 : month - 1;
109
+ const prevYear = month === 0 ? year - 1 : year;
110
+ const prevMonthDays = new Date(prevYear, prevMonth + 1, 0).getDate();
111
+
112
+ let week = [];
113
+
114
+ // Add days from previous month to fill the beginning of the first week
115
+ for (let i = firstDay - 1; i >= 0; i--) {
116
+ week.push({
117
+ dayNumber: prevMonthDays - i,
118
+ date: new Date(prevYear, prevMonth, prevMonthDays - i),
119
+ isToday: false,
120
+ hasEvents: false,
121
+ isOtherMonth: true,
122
+ });
123
+ }
124
+
125
+ // Add days of the current month
126
+ for (let day = 1; day <= daysInMonth; day++) {
127
+ const date = new Date(year, month, day);
128
+ const eventsForDate = getEventsForDate(date);
129
+
130
+ week.push({
131
+ dayNumber: day,
132
+ date: date,
133
+ isToday: isSameDay(date, new Date()),
134
+ hasEvents: eventsForDate.length > 0,
135
+ isOtherMonth: false,
136
+ });
137
+
138
+ if (week.length === 7) {
139
+ monthData.weeks.push(week);
140
+ week = [];
141
+ }
142
+ }
143
+
144
+ // Add days from next month to complete the last week
145
+ let nextMonthDay = 1;
146
+ while (week.length < 7) {
147
+ const nextMonth = month === 11 ? 0 : month + 1;
148
+ const nextYear = month === 11 ? year + 1 : year;
149
+
150
+ week.push({
151
+ dayNumber: nextMonthDay,
152
+ date: new Date(nextYear, nextMonth, nextMonthDay),
153
+ isToday: false,
154
+ hasEvents: false,
155
+ isOtherMonth: true,
156
+ });
157
+ nextMonthDay++;
158
+ }
159
+
160
+ if (week.length > 0) {
161
+ monthData.weeks.push(week);
162
+ }
163
+
164
+ months.push(monthData);
165
+ }
166
+
167
+ return months;
168
+ });
169
+
170
+ // Handle month click in year view
171
+ const onMonthClick = (monthData) => {
172
+ emit("monthClick", monthData);
173
+ };
174
+ </script>
175
+
176
+ <style scoped>
177
+ /* Year View Styles */
178
+ .year-view {
179
+ padding: 20px;
180
+ overflow-y: auto;
181
+ }
182
+
183
+ .year-grid {
184
+ display: grid;
185
+ grid-template-columns: repeat(4, 1fr);
186
+ gap: 20px;
187
+ width: 100%;
188
+ }
189
+
190
+ .mini-month {
191
+ border: 1px solid #e0e0e0;
192
+ border-radius: 8px;
193
+ overflow: hidden;
194
+ cursor: pointer;
195
+ transition: all 0.2s ease;
196
+ background: white;
197
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
198
+ }
199
+
200
+ .mini-month:hover {
201
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
202
+ transform: translateY(-1px);
203
+ }
204
+
205
+ .mini-month-header {
206
+ background-color: #1976d2;
207
+ color: white;
208
+ padding: 12px;
209
+ text-align: center;
210
+ font-weight: 600;
211
+ font-size: 1rem;
212
+ }
213
+
214
+ .mini-month-grid {
215
+ padding: 12px;
216
+ }
217
+
218
+ .mini-weekday-header {
219
+ display: grid;
220
+ grid-template-columns: repeat(7, 1fr);
221
+ gap: 2px;
222
+ margin-bottom: 4px;
223
+ }
224
+
225
+ .mini-weekday {
226
+ text-align: center;
227
+ font-size: 0.7rem;
228
+ font-weight: 600;
229
+ color: #666;
230
+ padding: 4px 0;
231
+ }
232
+
233
+ .mini-week {
234
+ display: grid;
235
+ grid-template-columns: repeat(7, 1fr);
236
+ gap: 2px;
237
+ margin-bottom: 2px;
238
+ }
239
+
240
+ .mini-day {
241
+ aspect-ratio: 1;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: center;
245
+ font-size: 0.8rem;
246
+ border-radius: 4px;
247
+ cursor: pointer;
248
+ transition: all 0.15s ease;
249
+ min-height: 24px;
250
+ }
251
+
252
+ .mini-day:hover {
253
+ background-color: #e3f2fd;
254
+ }
255
+
256
+ .mini-day.current-month {
257
+ color: #333;
258
+ font-weight: 500;
259
+ }
260
+
261
+ .mini-day.has-events {
262
+ background-color: #e3f2fd;
263
+ color: #1976d2;
264
+ font-weight: 600;
265
+ position: relative;
266
+ }
267
+
268
+ .mini-day.has-events::after {
269
+ content: "";
270
+ position: absolute;
271
+ bottom: 2px;
272
+ left: 50%;
273
+ transform: translateX(-50%);
274
+ width: 4px;
275
+ height: 4px;
276
+ background-color: #1976d2;
277
+ border-radius: 50%;
278
+ }
279
+
280
+ .mini-day.today {
281
+ background-color: #1976d2;
282
+ color: white;
283
+ font-weight: 700;
284
+ box-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
285
+ }
286
+
287
+ .mini-day.today.has-events::after {
288
+ background-color: white;
289
+ }
290
+
291
+ .mini-day.other-month {
292
+ color: #ccc;
293
+ font-weight: 400;
294
+ }
295
+ </style>
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <v-card width="100%">
3
+ <v-toolbar>
4
+ <v-row no-gutters class="fill-height px-6" align="center">
5
+ <span class="font-weight-bold text-h5">
6
+ {{ props.title }}
7
+ </span>
8
+ </v-row>
9
+ </v-toolbar>
10
+ <v-card-text style="max-height: 100vh; overflow-y: auto">
11
+ <v-form v-model="validForm" :disabled="disable">
12
+ <v-row no-gutters>
13
+ <v-col cols="12" class="mt-2">
14
+ <v-row no-gutters>
15
+ <InputLabel class="text-capitalize" title="Type" required />
16
+ <v-col cols="12">
17
+ <v-autocomplete
18
+ v-model="office.type"
19
+ density="comfortable"
20
+ :rules="[requiredRule]"
21
+ :items="officeTypes"
22
+ ></v-autocomplete>
23
+ </v-col>
24
+ </v-row>
25
+ </v-col>
26
+
27
+ <v-col cols="12" class="mt-2">
28
+ <v-row no-gutters>
29
+ <InputLabel class="text-capitalize" title="Code" required />
30
+ <v-col cols="12">
31
+ <v-text-field
32
+ v-model="office.code"
33
+ density="comfortable"
34
+ :rules="[requiredRule]"
35
+ ></v-text-field>
36
+ </v-col>
37
+ </v-row>
38
+ </v-col>
39
+
40
+ <v-col cols="12" class="mt-2">
41
+ <v-row no-gutters>
42
+ <InputLabel class="text-capitalize" title="Name" required />
43
+ <v-col cols="12">
44
+ <v-text-field
45
+ v-model="office.name"
46
+ density="comfortable"
47
+ :rules="[requiredRule]"
48
+ ></v-text-field>
49
+ </v-col>
50
+ </v-row>
51
+ </v-col>
52
+
53
+ <v-col v-if="hasParent" cols="12" class="mt-2">
54
+ <v-row no-gutters>
55
+ <InputLabel class="text-capitalize" title="Parent" required />
56
+ <v-col cols="12">
57
+ <v-autocomplete
58
+ v-model="office.parent"
59
+ density="comfortable"
60
+ :rules="[requiredRule]"
61
+ ></v-autocomplete>
62
+ </v-col>
63
+ </v-row>
64
+ </v-col>
65
+
66
+ <v-col cols="12" class="mt-2">
67
+ <v-checkbox v-model="createMore" density="comfortable" hide-details>
68
+ <template #label>
69
+ <span class="text-subtitle-2 font-weight-bold">
70
+ Create more
71
+ </span>
72
+ </template>
73
+ </v-checkbox>
74
+ </v-col>
75
+
76
+ <v-col cols="12" class="my-2">
77
+ <v-row no-gutters>
78
+ <v-col cols="12" class="text-center">
79
+ <span
80
+ class="text-none text-subtitle-2 font-weight-medium text-error"
81
+ >
82
+ {{ message }}
83
+ </span>
84
+ </v-col>
85
+ </v-row>
86
+ </v-col>
87
+ </v-row>
88
+ </v-form>
89
+ </v-card-text>
90
+
91
+ <v-toolbar density="compact">
92
+ <v-row no-gutters>
93
+ <v-col cols="6">
94
+ <v-btn
95
+ tile
96
+ block
97
+ variant="text"
98
+ class="text-none"
99
+ size="48"
100
+ @click="cancel"
101
+ >
102
+ Cancel
103
+ </v-btn>
104
+ </v-col>
105
+
106
+ <v-col cols="6">
107
+ <v-btn
108
+ tile
109
+ block
110
+ variant="flat"
111
+ color="black"
112
+ class="text-none"
113
+ size="48"
114
+ :disabled="!validForm"
115
+ @click="submit"
116
+ >
117
+ Submit
118
+ </v-btn>
119
+ </v-col>
120
+ </v-row>
121
+ </v-toolbar>
122
+ </v-card>
123
+ </template>
124
+
125
+ <script setup lang="ts">
126
+ const props = defineProps({
127
+ title: {
128
+ type: String,
129
+ default: "Office Form",
130
+ },
131
+ });
132
+
133
+ const emit = defineEmits(["cancel", "success", "success:create-more"]);
134
+
135
+ const validForm = ref(false);
136
+
137
+ const office = ref({
138
+ type: "",
139
+ code: "",
140
+ parent: "",
141
+ name: "",
142
+ path: "",
143
+ status: "active",
144
+ });
145
+
146
+ watchEffect(() => {
147
+ if (office.value.code) {
148
+ office.value.path = office.value.code.toUpperCase();
149
+ }
150
+ });
151
+
152
+ const officeTypes = ["office", "section", "unit"];
153
+
154
+ const hasParent = computed(() => {
155
+ return ["section", "unit"].includes(office.value.type);
156
+ });
157
+
158
+ const createMore = ref(false);
159
+ const disable = ref(false);
160
+
161
+ const { requiredRule } = useUtils();
162
+
163
+ const message = ref("");
164
+ const type = defineModel("type", {
165
+ type: String,
166
+ default: "",
167
+ });
168
+
169
+ const { add } = useOffice();
170
+
171
+ async function submit() {
172
+ disable.value = true;
173
+ try {
174
+ await add(office.value);
175
+ if (createMore.value) {
176
+ message.value = "";
177
+ emit("success:create-more");
178
+ return;
179
+ }
180
+
181
+ emit("success");
182
+ } catch (error: any) {
183
+ message.value = error.response._data.message;
184
+ } finally {
185
+ disable.value = false;
186
+ }
187
+ }
188
+
189
+ function cancel() {
190
+ createMore.value = false;
191
+ message.value = "";
192
+ emit("cancel");
193
+ }
194
+ </script>
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12" class="mb-2">
4
+ <v-row no-gutters>
5
+ <v-btn
6
+ class="text-none mr-2"
7
+ rounded="pill"
8
+ variant="tonal"
9
+ @click="dialogCreate = true"
10
+ size="large"
11
+ >
12
+ Add
13
+ </v-btn>
14
+ </v-row>
15
+ </v-col>
16
+
17
+ <v-col cols="12">
18
+ <v-card width="100%" variant="outlined" border="thin" rounded="lg">
19
+ <v-toolbar density="compact" color="grey-lighten-4">
20
+ <template #prepend>
21
+ <v-btn fab icon density="comfortable" @click="">
22
+ <v-icon>mdi-refresh</v-icon>
23
+ </v-btn>
24
+ </template>
25
+
26
+ <template #append>
27
+ <v-row no-gutters justify="end" align="center">
28
+ <span class="mr-2 text-caption text-fontgray">
29
+ {{ pageRange }}
30
+ </span>
31
+ <local-pagination
32
+ v-model="page"
33
+ :length="pages"
34
+ @update:value=""
35
+ />
36
+ </v-row>
37
+ </template>
38
+ </v-toolbar>
39
+
40
+ <v-data-table
41
+ :headers="headers"
42
+ :items="items"
43
+ item-value="_id"
44
+ items-per-page="20"
45
+ fixed-header
46
+ hide-default-footer
47
+ @click:row="tableRowClickHandler"
48
+ style="max-height: calc(100vh - (200px))"
49
+ >
50
+ </v-data-table>
51
+ </v-card>
52
+ </v-col>
53
+
54
+ <!-- dialog -->
55
+ <v-dialog v-model="dialogCreate" persistent width="450">
56
+ <OfficeForm
57
+ title="Add"
58
+ @cancel="dialogCreate = false"
59
+ @success="successHandler()"
60
+ />
61
+ </v-dialog>
62
+ </v-row>
63
+ </template>
64
+
65
+ <script setup lang="ts">
66
+ definePageMeta({
67
+ middleware: ["01-auth", "org"],
68
+ secured: true,
69
+ });
70
+
71
+ const headers = [
72
+ {
73
+ title: "Code",
74
+ value: "code",
75
+ width: "120px",
76
+ },
77
+ {
78
+ title: "Name",
79
+ value: "name",
80
+ },
81
+ {
82
+ title: "Type",
83
+ value: "type",
84
+ width: "120px",
85
+ },
86
+ {
87
+ title: "Status",
88
+ value: "status",
89
+ width: "120px",
90
+ },
91
+ ];
92
+
93
+ const page = ref(1);
94
+ const pages = ref(0);
95
+ const pageRange = ref("-- - -- of --");
96
+
97
+ const items = ref<Array<Record<string, any>>>([]);
98
+
99
+ const { getAll } = useOffice();
100
+
101
+ const { data: getItems, refresh: refreshOfficeItems } = useLazyAsyncData(
102
+ "get-offices-" + page.value,
103
+ () => getAll(),
104
+ {
105
+ watch: [page],
106
+ immediate: true,
107
+ }
108
+ );
109
+
110
+ watchEffect(() => {
111
+ if (getItems.value) {
112
+ items.value = getItems.value.items;
113
+ pages.value = getItems.value.pages;
114
+ pageRange.value = getItems.value.pageRange;
115
+ }
116
+ });
117
+
118
+ function tableRowClickHandler(_: any, data: any) {}
119
+
120
+ const dialogCreate = ref(false);
121
+
122
+ function successHandler() {
123
+ dialogCreate.value = false;
124
+ refreshOfficeItems();
125
+ }
126
+ </script>