@bagelink/vue 1.12.19 → 1.12.25
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/dist/components/calendar/CalendarTypes.d.ts.map +1 -1
- package/dist/components/calendar/Index.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
- package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ArrayInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/dialog/DialogForm.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/index.cjs +124 -124
- package/dist/index.mjs +13282 -13011
- package/dist/plugins/bagel.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/Pagination.vue +1 -1
- package/src/components/calendar/CalendarTypes.ts +11 -0
- package/src/components/calendar/Index.vue +25 -4
- package/src/components/calendar/views/WeekView.vue +260 -16
- package/src/components/dataTable/DataTable.vue +10 -0
- package/src/components/form/inputs/ArrayInput.vue +4 -3
- package/src/components/layout/AppLayout.vue +4 -2
- package/src/components/layout/AppSidebar.vue +21 -8
- package/src/dialog/DialogForm.vue +7 -9
- package/src/form-flow/FormFlow.vue +14 -1
- package/src/form-flow/MultiStepForm.vue +1 -1
- package/src/form-flow/form-flow.ts +2 -2
- package/src/i18n/locales/en.json +48 -0
- package/src/i18n/locales/es.json +1 -0
- package/src/i18n/locales/fr.json +1 -0
- package/src/i18n/locales/he.json +48 -0
- package/src/i18n/locales/it.json +1 -0
- package/src/i18n/locales/ru.json +1 -0
- package/src/plugins/bagel.ts +2 -0
- package/src/plugins/useModal.ts +2 -2
- package/src/styles/buttons.css +6 -4
- package/src/styles/layout.css +48 -2
- package/src/styles/mobilLayout.css +41 -0
- package/src/styles/text.css +1759 -1755
- package/src/types/index.ts +4 -3
- package/src/utils/elementUtils.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAEvE,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bagelink/vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.12.
|
|
4
|
+
"version": "1.12.25",
|
|
5
5
|
"description": "Bagel core sdk packages",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bagel Studio",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"signature_pad": "^5.0.9",
|
|
91
91
|
"vue-i18n": "^11.2.8",
|
|
92
92
|
"vue-toastification": "^2.0.0-rc.5",
|
|
93
|
-
"@bagelink/utils": "1.12.
|
|
93
|
+
"@bagelink/utils": "1.12.25"
|
|
94
94
|
},
|
|
95
95
|
"scripts": {
|
|
96
96
|
"dev": "tsx watch src/index.ts",
|
|
@@ -14,11 +14,22 @@ export type CalendarView = 'Week' | 'Month' | 'Day' | 'Agenda'
|
|
|
14
14
|
|
|
15
15
|
export type WeekStart = 'Sunday' | 'Monday'
|
|
16
16
|
|
|
17
|
+
export interface AvailabilitySlot {
|
|
18
|
+
start_time: Date
|
|
19
|
+
end_time: Date
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export interface CalendarProps {
|
|
18
23
|
events?: CalendarEvent[]
|
|
19
24
|
startDate?: Date
|
|
20
25
|
view?: CalendarView
|
|
21
26
|
weekStart?: WeekStart
|
|
27
|
+
/** When true, renders availability blocks on the week grid */
|
|
28
|
+
availabilityMode?: boolean
|
|
29
|
+
/** When true, allows drag-to-paint editing of availability (requires availabilityMode) */
|
|
30
|
+
availabilityEditable?: boolean
|
|
31
|
+
/** Existing availability slots to display as painted blocks */
|
|
32
|
+
availabilitySlots?: AvailabilitySlot[]
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
export interface CalendarViewState {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Component } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type { AvailabilitySlot, CalendarEvent, CalendarView, CalendarViewState, WeekStart } from './CalendarTypes'
|
|
4
4
|
import { timeDelta, Btn, ListItem, Dropdown, formatDate } from '@bagelink/vue'
|
|
5
5
|
import { ref, computed, onMounted } from 'vue'
|
|
6
6
|
import CalendarPopover from './CalendarPopover.vue'
|
|
@@ -9,11 +9,22 @@ import DayView from './views/DayView.vue'
|
|
|
9
9
|
import MonthView from './views/MonthView.vue'
|
|
10
10
|
import WeekView from './views/WeekView.vue'
|
|
11
11
|
|
|
12
|
-
const props = withDefaults(defineProps<
|
|
12
|
+
const props = withDefaults(defineProps<{
|
|
13
|
+
events?: CalendarEvent[]
|
|
14
|
+
startDate?: Date
|
|
15
|
+
view?: CalendarView
|
|
16
|
+
weekStart?: WeekStart
|
|
17
|
+
availabilityMode?: boolean
|
|
18
|
+
availabilityEditable?: boolean
|
|
19
|
+
availabilitySlots?: AvailabilitySlot[]
|
|
20
|
+
}>(), {
|
|
13
21
|
startDate: () => new Date(),
|
|
14
22
|
view: 'Week',
|
|
15
23
|
weekStart: 'Sunday',
|
|
16
|
-
events: () => []
|
|
24
|
+
events: () => [],
|
|
25
|
+
availabilityMode: false,
|
|
26
|
+
availabilityEditable: false,
|
|
27
|
+
availabilitySlots: () => []
|
|
17
28
|
})
|
|
18
29
|
|
|
19
30
|
const emit = defineEmits<{
|
|
@@ -24,6 +35,7 @@ const emit = defineEmits<{
|
|
|
24
35
|
(e: 'dateChange', changeEvent: CalendarViewState): void
|
|
25
36
|
(e: 'viewChange', changeEvent: CalendarViewState): void
|
|
26
37
|
(e: 'ready', changeEvent: CalendarViewState): void
|
|
38
|
+
(e: 'availabilityChange', slots: AvailabilitySlot[]): void
|
|
27
39
|
}>()
|
|
28
40
|
|
|
29
41
|
const currentDate = ref(new Date(props.startDate))
|
|
@@ -117,7 +129,14 @@ defineExpose({
|
|
|
117
129
|
closePopover
|
|
118
130
|
})
|
|
119
131
|
|
|
120
|
-
onMounted(() => {
|
|
132
|
+
onMounted(() => {
|
|
133
|
+
console.log('[Calendar] props:', {
|
|
134
|
+
availabilityMode: props.availabilityMode,
|
|
135
|
+
availabilityEditable: props.availabilityEditable,
|
|
136
|
+
availabilitySlots: props.availabilitySlots?.length
|
|
137
|
+
})
|
|
138
|
+
emit('ready', state.value)
|
|
139
|
+
})
|
|
121
140
|
</script>
|
|
122
141
|
|
|
123
142
|
<template>
|
|
@@ -144,8 +163,10 @@ onMounted(() => { emit('ready', state.value) })
|
|
|
144
163
|
</div>
|
|
145
164
|
<component
|
|
146
165
|
:is="views[currentView]" :events="events" :start-date="currentDate" :week-start="weekStart"
|
|
166
|
+
:availability-mode="props.availabilityMode" :availability-editable="props.availabilityEditable" :availability-slots="props.availabilitySlots"
|
|
147
167
|
@event-click="handleEventClick" @event-create="handleEventCreate" @event-update="handleEventUpdate"
|
|
148
168
|
@event-delete="handleEventDelete" @date-change="handleDateChange" @open-popover="openPopover"
|
|
169
|
+
@availability-change="(slots: AvailabilitySlot[]) => emit('availabilityChange', slots)"
|
|
149
170
|
>
|
|
150
171
|
<template #eventContent="{ event, close }">
|
|
151
172
|
<slot name="eventContent" :event="event" :close="close" />
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { SetupContext, ComponentPublicInstance } from 'vue'
|
|
3
|
-
import type { CalendarEvent, WeekStart } from '../CalendarTypes'
|
|
3
|
+
import type { AvailabilitySlot, CalendarEvent, WeekStart } from '../CalendarTypes'
|
|
4
4
|
import { formatDate } from '@bagelink/vue'
|
|
5
|
-
import { ref, computed, onMounted, onUnmounted, useSlots, nextTick } from 'vue'
|
|
5
|
+
import { ref, computed, onMounted, onUnmounted, useSlots, nextTick, watch } from 'vue'
|
|
6
6
|
|
|
7
7
|
interface WeekViewEvent extends CalendarEvent {
|
|
8
8
|
top: number
|
|
@@ -17,16 +17,23 @@ const props = withDefaults(defineProps<{
|
|
|
17
17
|
events?: CalendarEvent[]
|
|
18
18
|
startDate?: Date
|
|
19
19
|
weekStart?: WeekStart
|
|
20
|
+
availabilityMode?: boolean
|
|
21
|
+
availabilityEditable?: boolean
|
|
22
|
+
availabilitySlots?: AvailabilitySlot[]
|
|
20
23
|
}>(), {
|
|
21
24
|
startDate: () => new Date(),
|
|
22
25
|
weekStart: 'Sunday',
|
|
23
|
-
events: () => []
|
|
26
|
+
events: () => [],
|
|
27
|
+
availabilityMode: false,
|
|
28
|
+
availabilityEditable: false,
|
|
29
|
+
availabilitySlots: () => []
|
|
24
30
|
})
|
|
25
31
|
|
|
26
32
|
const emit = defineEmits<{
|
|
27
33
|
(e: 'eventClick', event: CalendarEvent): void
|
|
28
34
|
(e: 'eventCreate', event: { start_time: Date, end_time: Date }): void
|
|
29
35
|
(e: 'openPopover', event: CalendarEvent, position?: { top: number, left: number }): void
|
|
36
|
+
(e: 'availabilityChange', slots: AvailabilitySlot[]): void
|
|
30
37
|
}>()
|
|
31
38
|
|
|
32
39
|
const slots: SetupContext['slots'] = useSlots()
|
|
@@ -52,16 +59,24 @@ const dragState = ref({
|
|
|
52
59
|
endTime: undefined as Date | undefined
|
|
53
60
|
})
|
|
54
61
|
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
62
|
+
// ===== AVAILABILITY MODE =====
|
|
63
|
+
const AVAILABILITY_SNAP_MINUTES = 30
|
|
64
|
+
const localAvailability = ref<AvailabilitySlot[]>([])
|
|
65
|
+
const availDragState = ref({
|
|
66
|
+
isDragging: false,
|
|
67
|
+
mode: 'add' as 'add' | 'remove',
|
|
68
|
+
dayIndex: -1,
|
|
69
|
+
startMinute: 0,
|
|
70
|
+
currentMinute: 0
|
|
71
|
+
})
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
// Sync external availability slots to local state
|
|
74
|
+
watch(() => props.availabilitySlots, (slots) => {
|
|
75
|
+
localAvailability.value = slots.map(s => ({
|
|
76
|
+
start_time: new Date(s.start_time),
|
|
77
|
+
end_time: new Date(s.end_time)
|
|
78
|
+
}))
|
|
79
|
+
}, { immediate: true })
|
|
65
80
|
|
|
66
81
|
// Calculate week days based on start date and week start preference
|
|
67
82
|
const weekDays = computed(() => {
|
|
@@ -83,19 +98,197 @@ const weekDays = computed(() => {
|
|
|
83
98
|
return days
|
|
84
99
|
})
|
|
85
100
|
|
|
101
|
+
function snapMinutes(minutes: number): number {
|
|
102
|
+
return Math.round(minutes / AVAILABILITY_SNAP_MINUTES) * AVAILABILITY_SNAP_MINUTES
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getAvailabilitySlotsForDay(day: Date) {
|
|
106
|
+
return localAvailability.value.filter((slot) => {
|
|
107
|
+
const slotDate = new Date(slot.start_time)
|
|
108
|
+
return slotDate.getFullYear() === day.getFullYear()
|
|
109
|
+
&& slotDate.getMonth() === day.getMonth()
|
|
110
|
+
&& slotDate.getDate() === day.getDate()
|
|
111
|
+
}).map((slot) => {
|
|
112
|
+
const startMinutes = new Date(slot.start_time).getHours() * 60 + new Date(slot.start_time).getMinutes()
|
|
113
|
+
const endMinutes = new Date(slot.end_time).getHours() * 60 + new Date(slot.end_time).getMinutes()
|
|
114
|
+
return {
|
|
115
|
+
top: (startMinutes / slotDuration) * slotHeight,
|
|
116
|
+
height: ((endMinutes - startMinutes) / slotDuration) * slotHeight,
|
|
117
|
+
slot
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function availabilityPreviewForDay(dayIndex: number) {
|
|
123
|
+
if (!availDragState.value.isDragging || availDragState.value.dayIndex !== dayIndex) return null
|
|
124
|
+
const minM = Math.min(availDragState.value.startMinute, availDragState.value.currentMinute)
|
|
125
|
+
const maxM = Math.max(availDragState.value.startMinute, availDragState.value.currentMinute)
|
|
126
|
+
return {
|
|
127
|
+
top: (minM / slotDuration) * slotHeight,
|
|
128
|
+
height: ((maxM - minM) / slotDuration) * slotHeight,
|
|
129
|
+
mode: availDragState.value.mode
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isMinuteInAvailability(day: Date, minute: number): boolean {
|
|
134
|
+
return localAvailability.value.some((slot) => {
|
|
135
|
+
const slotDate = new Date(slot.start_time)
|
|
136
|
+
if (slotDate.getFullYear() !== day.getFullYear()
|
|
137
|
+
|| slotDate.getMonth() !== day.getMonth()
|
|
138
|
+
|| slotDate.getDate() !== day.getDate()) { return false
|
|
139
|
+
}
|
|
140
|
+
const startM = slotDate.getHours() * 60 + slotDate.getMinutes()
|
|
141
|
+
const endM = new Date(slot.end_time).getHours() * 60 + new Date(slot.end_time).getMinutes()
|
|
142
|
+
return minute >= startM && minute < endM
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function handleAvailMouseDown(e: MouseEvent, dayIndex: number) {
|
|
147
|
+
if (!props.availabilityEditable) return
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
e.stopPropagation()
|
|
150
|
+
const day = weekDays.value[dayIndex]
|
|
151
|
+
const minutes = snapMinutes(getMinutesFromY(e.clientY))
|
|
152
|
+
const isExisting = isMinuteInAvailability(day, minutes)
|
|
153
|
+
|
|
154
|
+
availDragState.value = {
|
|
155
|
+
isDragging: true,
|
|
156
|
+
mode: isExisting ? 'remove' : 'add',
|
|
157
|
+
dayIndex,
|
|
158
|
+
startMinute: minutes,
|
|
159
|
+
currentMinute: minutes + AVAILABILITY_SNAP_MINUTES
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
document.addEventListener('mousemove', handleAvailMouseMove)
|
|
163
|
+
document.addEventListener('mouseup', handleAvailMouseUp)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function handleAvailMouseMove(e: MouseEvent) {
|
|
167
|
+
if (!availDragState.value.isDragging) return
|
|
168
|
+
const minutes = snapMinutes(getMinutesFromY(e.clientY))
|
|
169
|
+
availDragState.value.currentMinute = minutes
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleAvailMouseUp() {
|
|
173
|
+
if (!availDragState.value.isDragging) return
|
|
174
|
+
const { dayIndex, startMinute, currentMinute, mode } = availDragState.value
|
|
175
|
+
const day = weekDays.value[dayIndex]
|
|
176
|
+
const minM = Math.min(startMinute, currentMinute)
|
|
177
|
+
const maxM = Math.max(startMinute, currentMinute)
|
|
178
|
+
|
|
179
|
+
if (maxM - minM < AVAILABILITY_SNAP_MINUTES) {
|
|
180
|
+
availDragState.value.isDragging = false
|
|
181
|
+
document.removeEventListener('mousemove', handleAvailMouseMove)
|
|
182
|
+
document.removeEventListener('mouseup', handleAvailMouseUp)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const newStart = new Date(day)
|
|
187
|
+
newStart.setHours(Math.floor(minM / 60), minM % 60, 0, 0)
|
|
188
|
+
const newEnd = new Date(day)
|
|
189
|
+
newEnd.setHours(Math.floor(maxM / 60), maxM % 60, 0, 0)
|
|
190
|
+
|
|
191
|
+
if (mode === 'add') {
|
|
192
|
+
// Add and merge overlapping
|
|
193
|
+
const otherDays = localAvailability.value.filter((s) => {
|
|
194
|
+
const d = new Date(s.start_time)
|
|
195
|
+
return d.getFullYear() !== day.getFullYear()
|
|
196
|
+
|| d.getMonth() !== day.getMonth()
|
|
197
|
+
|| d.getDate() !== day.getDate()
|
|
198
|
+
})
|
|
199
|
+
const sameDaySlots = localAvailability.value.filter((s) => {
|
|
200
|
+
const d = new Date(s.start_time)
|
|
201
|
+
return d.getFullYear() === day.getFullYear()
|
|
202
|
+
&& d.getMonth() === day.getMonth()
|
|
203
|
+
&& d.getDate() === day.getDate()
|
|
204
|
+
})
|
|
205
|
+
sameDaySlots.push({ start_time: newStart, end_time: newEnd })
|
|
206
|
+
// Sort and merge
|
|
207
|
+
sameDaySlots.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime())
|
|
208
|
+
const merged: AvailabilitySlot[] = []
|
|
209
|
+
for (const s of sameDaySlots) {
|
|
210
|
+
if (merged.length === 0) {
|
|
211
|
+
merged.push({ start_time: new Date(s.start_time), end_time: new Date(s.end_time) })
|
|
212
|
+
} else {
|
|
213
|
+
const last = merged[merged.length - 1]
|
|
214
|
+
if (new Date(s.start_time).getTime() <= new Date(last.end_time).getTime()) {
|
|
215
|
+
last.end_time = new Date(Math.max(new Date(last.end_time).getTime(), new Date(s.end_time).getTime()))
|
|
216
|
+
} else {
|
|
217
|
+
merged.push({ start_time: new Date(s.start_time), end_time: new Date(s.end_time) })
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
localAvailability.value = [...otherDays, ...merged]
|
|
222
|
+
} else {
|
|
223
|
+
// Remove: subtract the range from existing slots
|
|
224
|
+
const result: AvailabilitySlot[] = []
|
|
225
|
+
for (const s of localAvailability.value) {
|
|
226
|
+
const d = new Date(s.start_time)
|
|
227
|
+
if (d.getFullYear() !== day.getFullYear()
|
|
228
|
+
|| d.getMonth() !== day.getMonth()
|
|
229
|
+
|| d.getDate() !== day.getDate()) {
|
|
230
|
+
result.push(s)
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
const sStart = new Date(s.start_time).getTime()
|
|
234
|
+
const sEnd = new Date(s.end_time).getTime()
|
|
235
|
+
const rStart = newStart.getTime()
|
|
236
|
+
const rEnd = newEnd.getTime()
|
|
237
|
+
|
|
238
|
+
// No overlap
|
|
239
|
+
if (sEnd <= rStart || sStart >= rEnd) {
|
|
240
|
+
result.push(s)
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
// Left remainder
|
|
244
|
+
if (sStart < rStart) {
|
|
245
|
+
result.push({ start_time: new Date(sStart), end_time: new Date(rStart) })
|
|
246
|
+
}
|
|
247
|
+
// Right remainder
|
|
248
|
+
if (sEnd > rEnd) {
|
|
249
|
+
result.push({ start_time: new Date(rEnd), end_time: new Date(sEnd) })
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
localAvailability.value = result
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
emit('availabilityChange', localAvailability.value)
|
|
256
|
+
availDragState.value.isDragging = false
|
|
257
|
+
document.removeEventListener('mousemove', handleAvailMouseMove)
|
|
258
|
+
document.removeEventListener('mouseup', handleAvailMouseUp)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Time indicators
|
|
262
|
+
const currentTimeTop = ref(0)
|
|
263
|
+
const isToday = ref(false)
|
|
264
|
+
const currentTimeInterval = ref<any>()
|
|
265
|
+
|
|
266
|
+
const timeSlotsContainer = ref<HTMLElement>()
|
|
267
|
+
const calendarGrid = ref<HTMLElement>()
|
|
268
|
+
const dayColumns = ref<HTMLElement[]>([])
|
|
269
|
+
const dayColumnsContainer = ref<HTMLElement>()
|
|
270
|
+
const scrollableContainer = ref<HTMLElement>()
|
|
271
|
+
|
|
272
|
+
function getMinutesFromY(clientY: number): number {
|
|
273
|
+
if (!dayColumnsContainer.value) return 0
|
|
274
|
+
const rect = dayColumnsContainer.value.getBoundingClientRect()
|
|
275
|
+
const relativeY = clientY - rect.top
|
|
276
|
+
return Math.max(0, Math.min((relativeY / slotHeight) * slotDuration, 24 * 60))
|
|
277
|
+
}
|
|
278
|
+
|
|
86
279
|
// Generate hourly time slots
|
|
87
280
|
const timeSlots = computed(() => {
|
|
88
|
-
const
|
|
281
|
+
const items = []
|
|
89
282
|
for (let hour = timeRange.start; hour < timeRange.end; hour++) {
|
|
90
283
|
for (let minute = 0; minute < 60; minute += slotDuration) {
|
|
91
|
-
|
|
284
|
+
items.push({
|
|
92
285
|
hour,
|
|
93
286
|
minute,
|
|
94
287
|
time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`
|
|
95
288
|
})
|
|
96
289
|
}
|
|
97
290
|
}
|
|
98
|
-
return
|
|
291
|
+
return items
|
|
99
292
|
})
|
|
100
293
|
|
|
101
294
|
// Calculate drag preview positioning using the same formula as events
|
|
@@ -410,6 +603,11 @@ function scrollToTime(time: number) {
|
|
|
410
603
|
|
|
411
604
|
// Lifecycle hooks
|
|
412
605
|
onMounted(() => {
|
|
606
|
+
console.log('[WeekView] mounted with props:', {
|
|
607
|
+
availabilityMode: props.availabilityMode,
|
|
608
|
+
availabilityEditable: props.availabilityEditable,
|
|
609
|
+
availabilitySlotsCount: props.availabilitySlots?.length
|
|
610
|
+
})
|
|
413
611
|
updateCurrentTimeIndicator()
|
|
414
612
|
currentTimeInterval.value = setInterval(updateCurrentTimeIndicator, 60000)
|
|
415
613
|
|
|
@@ -420,6 +618,8 @@ onMounted(() => {
|
|
|
420
618
|
onUnmounted(() => {
|
|
421
619
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
422
620
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
621
|
+
document.removeEventListener('mousemove', handleAvailMouseMove)
|
|
622
|
+
document.removeEventListener('mouseup', handleAvailMouseUp)
|
|
423
623
|
|
|
424
624
|
if (currentTimeInterval.value) {
|
|
425
625
|
clearInterval(currentTimeInterval.value)
|
|
@@ -466,8 +666,29 @@ onUnmounted(() => {
|
|
|
466
666
|
|
|
467
667
|
<div
|
|
468
668
|
v-for="(day, index) in weekDays" :key="day.toISOString()" :ref="el => setDayColumnRef(el, index)"
|
|
469
|
-
class="day-column top bottom border-start relative"
|
|
669
|
+
class="day-column top bottom border-start relative"
|
|
670
|
+
@mousedown="props.availabilityEditable ? handleAvailMouseDown($event, index) : handleMouseDown($event, day)"
|
|
470
671
|
>
|
|
672
|
+
<!-- Availability painted blocks (behind events) -->
|
|
673
|
+
<template v-if="props.availabilityMode">
|
|
674
|
+
<div
|
|
675
|
+
v-for="(block, bi) in getAvailabilitySlotsForDay(day)" :key="`avail-${bi}`"
|
|
676
|
+
class="availability-block absolute start end"
|
|
677
|
+
:class="{ 'availability-editable': props.availabilityEditable }"
|
|
678
|
+
:style="{ top: `${block.top}px`, height: `${block.height}px` }"
|
|
679
|
+
/>
|
|
680
|
+
<!-- Availability drag preview -->
|
|
681
|
+
<div
|
|
682
|
+
v-if="availabilityPreviewForDay(index)"
|
|
683
|
+
class="availability-preview absolute start end pointer-events-none"
|
|
684
|
+
:class="{ 'avail-remove': availabilityPreviewForDay(index)!.mode === 'remove' }"
|
|
685
|
+
:style="{
|
|
686
|
+
top: `${availabilityPreviewForDay(index)!.top}px`,
|
|
687
|
+
height: `${availabilityPreviewForDay(index)!.height}px`,
|
|
688
|
+
}"
|
|
689
|
+
/>
|
|
690
|
+
</template>
|
|
691
|
+
|
|
471
692
|
<template
|
|
472
693
|
v-for="event in processedEvents.filter(e =>
|
|
473
694
|
new Date(e.start_time).toDateString() === day.toDateString())" :key="event.id"
|
|
@@ -587,6 +808,29 @@ onUnmounted(() => {
|
|
|
587
808
|
justify-content: center; */
|
|
588
809
|
}
|
|
589
810
|
|
|
811
|
+
.availability-block {
|
|
812
|
+
background-color: rgba(76, 175, 80, 0.18);
|
|
813
|
+
border-inline-start: 3px solid rgba(76, 175, 80, 0.5);
|
|
814
|
+
z-index: 0;
|
|
815
|
+
pointer-events: none;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.availability-block.availability-editable {
|
|
819
|
+
background-color: rgba(33, 150, 243, 0.18);
|
|
820
|
+
border-inline-start: 3px solid rgba(33, 150, 243, 0.5);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.availability-preview {
|
|
824
|
+
background-color: rgba(76, 175, 80, 0.25);
|
|
825
|
+
border: 1px dashed rgba(76, 175, 80, 0.6);
|
|
826
|
+
z-index: 0;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.availability-preview.avail-remove {
|
|
830
|
+
background-color: rgba(244, 67, 54, 0.15);
|
|
831
|
+
border-color: rgba(244, 67, 54, 0.5);
|
|
832
|
+
}
|
|
833
|
+
|
|
590
834
|
@media (max-width: 910px) {
|
|
591
835
|
.weekGrid {
|
|
592
836
|
display: grid;
|
|
@@ -412,4 +412,14 @@ tbody tr {
|
|
|
412
412
|
tbody tr:hover {
|
|
413
413
|
background: var(--bgl-gray-light);
|
|
414
414
|
}
|
|
415
|
+
|
|
416
|
+
@media (max-width: 910px) {
|
|
417
|
+
.col {
|
|
418
|
+
max-width: 60vw !important;
|
|
419
|
+
}
|
|
420
|
+
.max-col-width {
|
|
421
|
+
max-width: 60vw !important;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
415
425
|
</style>
|
|
@@ -136,7 +136,7 @@ function getItemRef(i: number) {
|
|
|
136
136
|
{{ helpText }}
|
|
137
137
|
</p>
|
|
138
138
|
<template v-if="!collapsible">
|
|
139
|
-
<div v-for="(_, i) in items" :key="i" class="array-input-row grid align-items-center">
|
|
139
|
+
<div v-for="(_, i) in items" :key="i" class="array-input-row grid align-items-center pb-1">
|
|
140
140
|
<slot :item="getItemRef(i)" :index="i" />
|
|
141
141
|
<div v-if="allowDelete" class="ms-075 my-1 border-start h-100p ps-025">
|
|
142
142
|
<Btn
|
|
@@ -149,13 +149,14 @@ function getItemRef(i: number) {
|
|
|
149
149
|
<Draggable v-if="collapsible" :model-value="items" handle=".grab" :disabled="!allowReorder" class="grid gap-05 mb-05" @end="onDraggableEnd">
|
|
150
150
|
<template #default="{ index: i }">
|
|
151
151
|
<div class="border radius-1 overflow-hidden">
|
|
152
|
-
<div class="grid gap-
|
|
152
|
+
<div class="grid gap-0 grid-array-line align-items-center pe-025 hover">
|
|
153
153
|
<Btn
|
|
154
154
|
v-if="allowReorder" v-tooltip="resolveI18n('Drag to reorder')" flat thin icon="drag_indicator"
|
|
155
155
|
class="grab"
|
|
156
156
|
/>
|
|
157
|
+
<div v-else />
|
|
157
158
|
<Btn
|
|
158
|
-
full-width align-txt="start" class="px-
|
|
159
|
+
full-width align-txt="start" class="px-025 bg-transparent color-inherit" :ripple="false"
|
|
159
160
|
:icon="minimizedItems[i] ? 'expand_more' : 'expand_less'" @click="toggleItem(i)"
|
|
160
161
|
>
|
|
161
162
|
<p class="ellipsis-1">
|
|
@@ -4,15 +4,17 @@ import { ref, provide, onMounted, onUnmounted, computed } from 'vue'
|
|
|
4
4
|
interface Props {
|
|
5
5
|
sidebarWidth?: string
|
|
6
6
|
sidebarCardStyle?: boolean
|
|
7
|
+
defaultOpen?: boolean
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
const props = withDefaults(defineProps<Props>(), {
|
|
10
11
|
sidebarWidth: '260px',
|
|
11
|
-
sidebarCardStyle: false
|
|
12
|
+
sidebarCardStyle: false,
|
|
13
|
+
defaultOpen: true,
|
|
12
14
|
})
|
|
13
15
|
|
|
14
16
|
// Menu state
|
|
15
|
-
const isOpen = ref(
|
|
17
|
+
const isOpen = ref(props.defaultOpen)
|
|
16
18
|
const isMobile = ref(false)
|
|
17
19
|
|
|
18
20
|
// Check if mobile
|
|
@@ -7,7 +7,7 @@ import { resolveI18n } from '../../i18n'
|
|
|
7
7
|
|
|
8
8
|
// Extended interface for links with active route tracking
|
|
9
9
|
interface LinkWithAction extends NavLink {
|
|
10
|
-
activeRoutes?: string[] //
|
|
10
|
+
activeRoutes?: string[] // Routes that should be marked as active for this link
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
@@ -23,10 +23,14 @@ interface Props {
|
|
|
23
23
|
name?: string
|
|
24
24
|
frame?: boolean
|
|
25
25
|
activeRoutes?: string[]
|
|
26
|
+
centerlinks?: boolean
|
|
27
|
+
defaultOpen?: boolean
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const props = withDefaults(defineProps<Props>(), {
|
|
29
31
|
card: true,
|
|
32
|
+
centerlinks: false,
|
|
33
|
+
defaultOpen: true,
|
|
30
34
|
bgColor: 'var(--bgl-white)',
|
|
31
35
|
textColor: 'var(--bgl-black)',
|
|
32
36
|
activeColor: 'var(--bgl-black)',
|
|
@@ -40,9 +44,12 @@ const route = useRoute()
|
|
|
40
44
|
const isTransitioning = ref(false)
|
|
41
45
|
|
|
42
46
|
// Inject menu state from parent
|
|
47
|
+
const _fallbackIsOpen = ref(props.defaultOpen)
|
|
48
|
+
const _fallbackIsMobile = ref(false)
|
|
49
|
+
|
|
43
50
|
const menuState = inject('menuState', {
|
|
44
|
-
isOpen:
|
|
45
|
-
isMobile:
|
|
51
|
+
isOpen: _fallbackIsOpen,
|
|
52
|
+
isMobile: _fallbackIsMobile,
|
|
46
53
|
closeOnMobile: () => void 0,
|
|
47
54
|
sidebarWidth: '260px',
|
|
48
55
|
sidebarCollapsedWidth: '66px',
|
|
@@ -160,7 +167,7 @@ const sidebarStyles = computed(() => {
|
|
|
160
167
|
>
|
|
161
168
|
<!-- Logo/Brand -->
|
|
162
169
|
<router-link
|
|
163
|
-
to="/" class="decoration-none flex
|
|
170
|
+
to="/" class="decoration-none flex siderbarLogoWrap" :class="{
|
|
164
171
|
'gap-05': menuState.isOpen.value,
|
|
165
172
|
'gap-0': !menuState.isOpen.value,
|
|
166
173
|
}"
|
|
@@ -175,7 +182,7 @@ const sidebarStyles = computed(() => {
|
|
|
175
182
|
</router-link>
|
|
176
183
|
|
|
177
184
|
<!-- Navigation Links -->
|
|
178
|
-
<nav class="sidebar-nav flex column flex-stretch gap-025 align-items-start scrollbar-gutter-stable">
|
|
185
|
+
<nav class="sidebar-nav flex column flex-stretch gap-025 align-items-start scrollbar-gutter-stable" :class="{ 'justify-content-center': props.centerlinks }">
|
|
179
186
|
<Btn
|
|
180
187
|
v-for="link in props.navLinks" :key="link.to"
|
|
181
188
|
:title="!menuState.isOpen.value && !menuState.isMobile.value ? resolveI18n(link.label) : ''" fullWidth
|
|
@@ -183,7 +190,8 @@ const sidebarStyles = computed(() => {
|
|
|
183
190
|
:style="{
|
|
184
191
|
backgroundColor: isActiveRoute(link) ? props.activeColor : props.bgColor,
|
|
185
192
|
color: isActiveRoute(link) ? 'white' : props.textColor,
|
|
186
|
-
}"
|
|
193
|
+
}"
|
|
194
|
+
:to="link.to || '/'" @click="link.action"
|
|
187
195
|
>
|
|
188
196
|
<Icon :name="link.icon" size="1.2" />
|
|
189
197
|
<span class="nav-text">
|
|
@@ -196,8 +204,13 @@ const sidebarStyles = computed(() => {
|
|
|
196
204
|
<!-- Footer Links -->
|
|
197
205
|
<Btn
|
|
198
206
|
v-for="link in props.footerLinks" :key="link.to || link.label"
|
|
199
|
-
:title="!menuState.isOpen.value && !menuState.isMobile.value ? resolveI18n(link.label) : ''"
|
|
200
|
-
|
|
207
|
+
:title="!menuState.isOpen.value && !menuState.isMobile.value ? resolveI18n(link.label) : ''"
|
|
208
|
+
:style="{
|
|
209
|
+
color: isActiveRoute(link) ? 'white' : props.textColor,
|
|
210
|
+
}"
|
|
211
|
+
fullWidth
|
|
212
|
+
alignTxt="start"
|
|
213
|
+
flat :icon="link.icon" class="flex-shrink-0 px-1" :to="link.to" @click="link.action"
|
|
201
214
|
>
|
|
202
215
|
<span class="nav-text">
|
|
203
216
|
{{ resolveI18n(link.label) }}
|
|
@@ -108,15 +108,13 @@ function handleClose() {
|
|
|
108
108
|
:open="open" :title="title" :width="width" :position="position" :dismissable="dismissable"
|
|
109
109
|
@update:open="$emit('update:open', $event)" @close="handleClose"
|
|
110
110
|
>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
<FormFlow ref="formFlowRef" v-model="formData" :schema="schema" />
|
|
119
|
-
</form>
|
|
111
|
+
<!-- Error message -->
|
|
112
|
+
<div v-if="error" class="dialog-form-error bg-red-30 color-red radius-1 mb-1 txt14 px-1 py-075">
|
|
113
|
+
{{ error }}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Form fields -->
|
|
117
|
+
<FormFlow ref="formFlowRef" v-model="formData" :schema="schema" @submit="handleSubmit" />
|
|
120
118
|
|
|
121
119
|
<template #footer>
|
|
122
120
|
<!-- Delete button (left side) -->
|