@bagelink/vue 1.4.109 → 1.4.115
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/bin/generateFormSchema.ts +12 -12
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/ImportData.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts +6 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +39 -0
- package/dist/components/analytics/BarChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/KpiCard.vue.d.ts +24 -0
- package/dist/components/analytics/KpiCard.vue.d.ts.map +1 -0
- package/dist/components/analytics/LineChart.vue.d.ts +26 -0
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/PieChart.vue.d.ts +24 -0
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/index.d.ts +5 -0
- package/dist/components/analytics/index.d.ts.map +1 -0
- package/dist/components/calendar/Index.vue.d.ts.map +1 -1
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/views/MonthView.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/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +2 -0
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +6 -10
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +2 -2
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts +34 -0
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -0
- package/dist/components/layout/AppLayout.vue.d.ts +27 -0
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -0
- package/dist/components/layout/AppSidebar.vue.d.ts +44 -0
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/composables/useFormField.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts +2 -2
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/index.cjs +19 -19
- package/dist/index.mjs +10 -10
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts +25 -13
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts +11 -8
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/dist/utils/calendar/dateUtils.d.ts +21 -0
- package/dist/utils/calendar/dateUtils.d.ts.map +1 -1
- package/dist/utils/elementUtils.d.ts +5 -0
- package/dist/utils/elementUtils.d.ts.map +1 -1
- package/dist/utils/useSearch.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Card.vue +1 -2
- package/src/components/DataPreview.vue +1 -1
- package/src/components/ImportData.vue +94 -88
- package/src/components/ListItem.vue +32 -24
- package/src/components/analytics/BarChart.vue +153 -0
- package/src/components/analytics/KpiCard.vue +84 -0
- package/src/components/analytics/LineChart.vue +267 -0
- package/src/components/analytics/PieChart.vue +183 -0
- package/src/components/analytics/index.ts +4 -0
- package/src/components/calendar/Index.vue +15 -35
- package/src/components/calendar/views/MonthView.vue +84 -88
- package/src/components/calendar/views/WeekView.vue +143 -89
- package/src/components/dataTable/DataTable.vue +2 -3
- package/src/components/form/BagelForm.vue +27 -6
- package/src/components/form/inputs/DateInput.vue +2 -2
- package/src/components/form/inputs/DatePicker.vue +42 -48
- package/src/components/form/inputs/RadioGroup.vue +60 -35
- package/src/components/form/inputs/RichText/utils/media.ts +1 -2
- package/src/components/form/inputs/SelectInput.vue +94 -101
- package/src/components/form/inputs/Upload/upload.css +135 -138
- package/src/components/layout/AppContent.vue +125 -0
- package/src/components/layout/AppLayout.vue +124 -0
- package/src/components/layout/AppSidebar.vue +271 -0
- package/src/components/layout/index.ts +5 -0
- package/src/composables/useFormField.ts +6 -0
- package/src/composables/useSchemaField.ts +38 -10
- package/src/styles/inputs.css +9 -0
- package/src/styles/theme.css +2 -2
- package/src/types/BagelForm.ts +68 -13
- package/src/utils/BagelFormUtils.ts +49 -52
- package/src/utils/calendar/dateUtils.ts +71 -17
- package/src/utils/elementUtils.ts +23 -4
- package/src/utils/useSearch.ts +14 -7
- /package/src/components/{dialog → calendar}/index.ts +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { SetupContext } from 'vue'
|
|
3
3
|
import type { CalendarEvent } from '../CalendarTypes'
|
|
4
|
-
import {
|
|
4
|
+
import { formatDate } from '@bagelink/vue'
|
|
5
5
|
import { computed, useSlots } from 'vue'
|
|
6
6
|
|
|
7
7
|
interface MonthViewEvent {
|
|
@@ -115,17 +115,14 @@ function handleEventSelection(event: CalendarEvent, domEvent?: MouseEvent) {
|
|
|
115
115
|
</div>
|
|
116
116
|
<div class="month-grid">
|
|
117
117
|
<div
|
|
118
|
-
v-for="day in currentMonth"
|
|
119
|
-
:key="day.date.toISOString()"
|
|
120
|
-
class="day-cell"
|
|
121
|
-
:class="{
|
|
118
|
+
v-for="day in currentMonth" :key="day.date.toISOString()" class="day-cell" :class="{
|
|
122
119
|
'current-month': day.isCurrentMonth,
|
|
123
120
|
'today': day.isToday,
|
|
124
121
|
'other-month': !day.isCurrentMonth,
|
|
125
122
|
}"
|
|
126
123
|
>
|
|
127
124
|
<div class="day-number">
|
|
128
|
-
{{
|
|
125
|
+
{{ formatDate(day.date, { fmt: 'DD' }) }}
|
|
129
126
|
</div>
|
|
130
127
|
<div class="day-events">
|
|
131
128
|
<template v-if="isMobile">
|
|
@@ -133,9 +130,7 @@ function handleEventSelection(event: CalendarEvent, domEvent?: MouseEvent) {
|
|
|
133
130
|
</template>
|
|
134
131
|
<template v-else>
|
|
135
132
|
<div
|
|
136
|
-
v-for="event in day.events"
|
|
137
|
-
:key="event.id"
|
|
138
|
-
class="event-item"
|
|
133
|
+
v-for="event in day.events" :key="event.id" class="event-item"
|
|
139
134
|
:style="{ backgroundColor: event.color || 'var(--bgl-primary)' }"
|
|
140
135
|
@click.stop="handleEventSelection(event, $event)"
|
|
141
136
|
>
|
|
@@ -143,7 +138,7 @@ function handleEventSelection(event: CalendarEvent, domEvent?: MouseEvent) {
|
|
|
143
138
|
{{ event.title }}
|
|
144
139
|
</div>
|
|
145
140
|
<div class="event-time">
|
|
146
|
-
{{
|
|
141
|
+
{{ formatDate(event.start_time, 'HH:mm') }}
|
|
147
142
|
</div>
|
|
148
143
|
</div>
|
|
149
144
|
</template>
|
|
@@ -155,132 +150,133 @@ function handleEventSelection(event: CalendarEvent, domEvent?: MouseEvent) {
|
|
|
155
150
|
|
|
156
151
|
<style scoped>
|
|
157
152
|
.month-view {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
153
|
+
display: flex;
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
height: 100%;
|
|
156
|
+
overflow: hidden;
|
|
162
157
|
}
|
|
163
158
|
|
|
164
159
|
.month-header {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
display: grid;
|
|
161
|
+
grid-template-columns: repeat(7, 1fr);
|
|
162
|
+
border-bottom: 1px solid var(--border-color);
|
|
168
163
|
}
|
|
169
164
|
|
|
170
165
|
.weekday {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
padding: 0.5rem;
|
|
167
|
+
text-align: center;
|
|
168
|
+
color: var(--text-muted);
|
|
174
169
|
}
|
|
175
170
|
|
|
176
171
|
.month-grid {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
172
|
+
display: grid;
|
|
173
|
+
grid-template-columns: repeat(7, 1fr);
|
|
174
|
+
grid-template-rows: repeat(6, 1fr);
|
|
175
|
+
flex-grow: 1;
|
|
176
|
+
overflow: auto;
|
|
177
|
+
border-inline-end: 1px solid var(--border-color);
|
|
183
178
|
|
|
184
179
|
}
|
|
185
180
|
|
|
186
181
|
.day-cell {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
182
|
+
border-inline-start: 1px solid var(--border-color);
|
|
183
|
+
border-bottom: 1px solid var(--border-color);
|
|
184
|
+
padding: 0.5rem;
|
|
185
|
+
min-height: 100px;
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
193
188
|
}
|
|
194
189
|
|
|
195
190
|
.day-number {
|
|
196
|
-
|
|
191
|
+
margin-bottom: 0.5rem;
|
|
197
192
|
}
|
|
198
193
|
|
|
199
194
|
.other-month {
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
background-color: var(--bgl-gray-light);
|
|
196
|
+
color: var(--bgl-gray);
|
|
202
197
|
}
|
|
203
198
|
|
|
204
199
|
.today {
|
|
205
|
-
|
|
200
|
+
background-color: var(--bgl-primary-light);
|
|
206
201
|
}
|
|
207
202
|
|
|
208
203
|
.today .day-number {
|
|
209
|
-
|
|
204
|
+
color: var(--bgl-primary);
|
|
210
205
|
}
|
|
211
206
|
|
|
212
207
|
.day-events {
|
|
213
|
-
|
|
214
|
-
|
|
208
|
+
flex-grow: 1;
|
|
209
|
+
overflow-y: auto;
|
|
215
210
|
}
|
|
216
211
|
|
|
217
212
|
.event-item {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
213
|
+
margin-bottom: 0.25rem;
|
|
214
|
+
padding: 0.25rem;
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
color: white;
|
|
217
|
+
font-size: 0.8rem;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
transition: all 0.2s ease;
|
|
225
220
|
}
|
|
226
221
|
|
|
227
222
|
.event-title {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
223
|
+
font-weight: 500;
|
|
224
|
+
white-space: nowrap;
|
|
225
|
+
overflow: hidden;
|
|
226
|
+
text-overflow: ellipsis;
|
|
232
227
|
}
|
|
233
228
|
|
|
234
229
|
.event-time {
|
|
235
|
-
|
|
236
|
-
|
|
230
|
+
font-size: 0.7rem;
|
|
231
|
+
opacity: 0.8;
|
|
237
232
|
}
|
|
238
233
|
|
|
239
234
|
.event-dot {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
235
|
+
width: 6px;
|
|
236
|
+
height: 6px;
|
|
237
|
+
background-color: var(--bgl-primary);
|
|
238
|
+
border-radius: 50%;
|
|
239
|
+
margin: 0.25rem auto;
|
|
245
240
|
}
|
|
246
241
|
|
|
247
242
|
.custom-popover {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
243
|
+
position: fixed;
|
|
244
|
+
z-index: 1000;
|
|
245
|
+
min-width: 250px;
|
|
246
|
+
max-width: 350px;
|
|
247
|
+
background-color: white;
|
|
248
|
+
border-radius: var(--btn-border-radius);
|
|
249
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
250
|
+
animation: fadeIn 0.2s ease;
|
|
251
|
+
transform-origin: center left;
|
|
257
252
|
}
|
|
258
253
|
|
|
259
254
|
@keyframes fadeIn {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
255
|
+
from {
|
|
256
|
+
opacity: 0;
|
|
257
|
+
transform: scale(0.95);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
to {
|
|
261
|
+
opacity: 1;
|
|
262
|
+
transform: scale(1);
|
|
263
|
+
}
|
|
268
264
|
}
|
|
269
265
|
|
|
270
266
|
@media (max-width: 768px) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
267
|
+
.day-cell {
|
|
268
|
+
min-height: 60px;
|
|
269
|
+
padding: 0.25rem;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.day-number {
|
|
273
|
+
font-size: 0.8rem;
|
|
274
|
+
margin-bottom: 0.25rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.weekday {
|
|
278
|
+
font-size: 0.8rem;
|
|
279
|
+
padding: 0.25rem;
|
|
280
|
+
}
|
|
285
281
|
}
|
|
286
282
|
</style>
|
|
@@ -36,6 +36,11 @@ const slotHeight = 60
|
|
|
36
36
|
const slotDuration = 60
|
|
37
37
|
const timeRange = { start: 0, end: 24 }
|
|
38
38
|
|
|
39
|
+
// Check if drag-to-create is enabled based on whether eventCreate handler is provided
|
|
40
|
+
const isDragToCreateEnabled = computed(() => {
|
|
41
|
+
return !!slots.eventCreate || emit.hasOwnProperty('eventCreate')
|
|
42
|
+
})
|
|
43
|
+
|
|
39
44
|
// Drag state
|
|
40
45
|
const dragState = ref({
|
|
41
46
|
isDragging: false,
|
|
@@ -92,11 +97,28 @@ const timeSlots = computed(() => {
|
|
|
92
97
|
return slots
|
|
93
98
|
})
|
|
94
99
|
|
|
100
|
+
// Calculate drag preview positioning using the same formula as events
|
|
101
|
+
const dragPreviewPosition = computed(() => {
|
|
102
|
+
if (!dragState.value.isDragging || !dragState.value.startTime || !dragState.value.endTime) {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Use the exact same calculation as events
|
|
107
|
+
const startMinutes = dragState.value.startTime.getHours() * 60 + dragState.value.startTime.getMinutes()
|
|
108
|
+
const endMinutes = dragState.value.endTime.getHours() * 60 + dragState.value.endTime.getMinutes()
|
|
109
|
+
|
|
110
|
+
const top = (startMinutes / slotDuration) * slotHeight
|
|
111
|
+
const height = Math.max(((endMinutes - startMinutes) / slotDuration) * slotHeight, 30)
|
|
112
|
+
|
|
113
|
+
return { top, height }
|
|
114
|
+
})
|
|
115
|
+
|
|
95
116
|
// Process events for display with positioning calculations
|
|
96
117
|
const processedEvents = computed(() => {
|
|
97
118
|
const events: WeekViewEvent[] = []
|
|
98
|
-
const
|
|
119
|
+
const dayEvents: { [dayIndex: number]: WeekViewEvent[] } = {}
|
|
99
120
|
|
|
121
|
+
// First pass: group events by day and calculate basic positioning
|
|
100
122
|
props.events.forEach((event) => {
|
|
101
123
|
const startDate = new Date(event.start_time)
|
|
102
124
|
const endDate = new Date(event.end_time)
|
|
@@ -109,7 +131,7 @@ const processedEvents = computed(() => {
|
|
|
109
131
|
|
|
110
132
|
if (dayIndex === -1) return
|
|
111
133
|
|
|
112
|
-
// Calculate vertical positioning
|
|
134
|
+
// Calculate vertical positioning
|
|
113
135
|
const startMinutes = startDate.getHours() * 60 + startDate.getMinutes()
|
|
114
136
|
const endMinutes = endDate.getHours() * 60 + endDate.getMinutes()
|
|
115
137
|
const top = (startMinutes / slotDuration) * slotHeight
|
|
@@ -125,26 +147,69 @@ const processedEvents = computed(() => {
|
|
|
125
147
|
position: 0
|
|
126
148
|
}
|
|
127
149
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!overlappingGroups[key]) {
|
|
131
|
-
overlappingGroups[key] = []
|
|
150
|
+
if (!dayEvents[dayIndex]) {
|
|
151
|
+
dayEvents[dayIndex] = []
|
|
132
152
|
}
|
|
133
|
-
|
|
153
|
+
dayEvents[dayIndex].push(weekEvent)
|
|
134
154
|
})
|
|
135
155
|
|
|
136
|
-
//
|
|
137
|
-
Object.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
// Second pass: detect and handle overlapping events for each day
|
|
157
|
+
Object.entries(dayEvents).forEach(([_, dayEventList]) => {
|
|
158
|
+
// Sort events by start time
|
|
159
|
+
dayEventList.sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime())
|
|
160
|
+
|
|
161
|
+
// Group overlapping events
|
|
162
|
+
const overlappingGroups: WeekViewEvent[][] = []
|
|
163
|
+
let currentGroup: WeekViewEvent[] = []
|
|
164
|
+
|
|
165
|
+
dayEventList.forEach((event) => {
|
|
166
|
+
if (currentGroup.length === 0) {
|
|
167
|
+
currentGroup = [event]
|
|
168
|
+
} else {
|
|
169
|
+
// Check if this event overlaps with any event in the current group
|
|
170
|
+
const overlaps = currentGroup.some((groupEvent) => {
|
|
171
|
+
const eventStart = new Date(event.start_time).getTime()
|
|
172
|
+
const eventEnd = new Date(event.end_time).getTime()
|
|
173
|
+
const groupEventStart = new Date(groupEvent.start_time).getTime()
|
|
174
|
+
const groupEventEnd = new Date(groupEvent.end_time).getTime()
|
|
175
|
+
|
|
176
|
+
return !(eventEnd <= groupEventStart || eventStart >= groupEventEnd)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
if (overlaps) {
|
|
180
|
+
currentGroup.push(event)
|
|
181
|
+
} else {
|
|
182
|
+
// No overlap, finalize current group and start new one
|
|
183
|
+
if (currentGroup.length > 0) {
|
|
184
|
+
overlappingGroups.push([...currentGroup])
|
|
185
|
+
}
|
|
186
|
+
currentGroup = [event]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Don't forget the last group
|
|
192
|
+
if (currentGroup.length > 0) {
|
|
193
|
+
overlappingGroups.push(currentGroup)
|
|
146
194
|
}
|
|
147
|
-
|
|
195
|
+
|
|
196
|
+
// Position overlapping events with elegant spacing
|
|
197
|
+
overlappingGroups.forEach((group) => {
|
|
198
|
+
if (group.length > 1) {
|
|
199
|
+
// Add some spacing between overlapping events (2% gap)
|
|
200
|
+
const gap = 2
|
|
201
|
+
const availableWidth = 100 - (gap * (group.length - 1))
|
|
202
|
+
const eventWidth = availableWidth / group.length
|
|
203
|
+
|
|
204
|
+
group.forEach((event, index) => {
|
|
205
|
+
event.overlappingEvents = group.length
|
|
206
|
+
event.position = index
|
|
207
|
+
event.width = eventWidth
|
|
208
|
+
event.left = index * (eventWidth + gap)
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
events.push(...group)
|
|
212
|
+
})
|
|
148
213
|
})
|
|
149
214
|
|
|
150
215
|
return events
|
|
@@ -152,6 +217,9 @@ const processedEvents = computed(() => {
|
|
|
152
217
|
|
|
153
218
|
// Mouse event handlers for drag-to-create functionality
|
|
154
219
|
function handleMouseDown(e: MouseEvent, day: Date) {
|
|
220
|
+
// Only allow drag-to-create if the feature is enabled
|
|
221
|
+
if (!isDragToCreateEnabled.value) return
|
|
222
|
+
|
|
155
223
|
if (!calendarGrid.value || !dayColumnsContainer.value) return
|
|
156
224
|
|
|
157
225
|
// Find the clicked day column
|
|
@@ -185,11 +253,8 @@ function handleMouseDown(e: MouseEvent, day: Date) {
|
|
|
185
253
|
y: e.clientY - dayColumnsRect.top
|
|
186
254
|
}
|
|
187
255
|
|
|
188
|
-
//
|
|
189
|
-
const
|
|
190
|
-
cleanDay.setHours(0, 0, 0, 0)
|
|
191
|
-
|
|
192
|
-
const startTime = getTimeFromPosition(e.clientY, cleanDay)
|
|
256
|
+
// Use the same positioning calculation as events for consistency
|
|
257
|
+
const startTime = getTimeFromPositionConsistent(e.clientY, day)
|
|
193
258
|
dragState.value.startTime = startTime
|
|
194
259
|
|
|
195
260
|
document.addEventListener('mousemove', handleMouseMove)
|
|
@@ -197,7 +262,7 @@ function handleMouseDown(e: MouseEvent, day: Date) {
|
|
|
197
262
|
}
|
|
198
263
|
|
|
199
264
|
function handleMouseMove(e: MouseEvent) {
|
|
200
|
-
if (!dragState.value.isDragging || !dragState.value.start || !dayColumns.value.length || !calendarGrid.value || !dayColumnsContainer.value) return
|
|
265
|
+
if (!isDragToCreateEnabled.value || !dragState.value.isDragging || !dragState.value.start || !dayColumns.value.length || !calendarGrid.value || !dayColumnsContainer.value) return
|
|
201
266
|
|
|
202
267
|
// Find which day column we're hovering over
|
|
203
268
|
let targetDayIndex = -1
|
|
@@ -215,50 +280,34 @@ function handleMouseMove(e: MouseEvent) {
|
|
|
215
280
|
if (targetDayIndex === -1) return
|
|
216
281
|
|
|
217
282
|
// Update which days are selected
|
|
218
|
-
const dayColumnsRect = dayColumnsContainer.value.getBoundingClientRect()
|
|
219
|
-
const minDayIndex = Math.min(dragState.value.startDay, targetDayIndex)
|
|
220
|
-
const maxDayIndex = Math.max(dragState.value.startDay, targetDayIndex)
|
|
221
|
-
|
|
222
|
-
// Get the left edge of the leftmost selected day
|
|
223
|
-
const leftDayEl = dayColumns.value[minDayIndex]
|
|
224
|
-
if (!leftDayEl) return
|
|
225
|
-
const leftDayRect = leftDayEl.getBoundingClientRect()
|
|
226
|
-
const leftEdge = leftDayRect.left - dayColumnsRect.left
|
|
227
|
-
|
|
228
|
-
// Get the right edge of the rightmost selected day
|
|
229
|
-
const rightDayEl = dayColumns.value[maxDayIndex]
|
|
230
|
-
if (!rightDayEl) return
|
|
231
|
-
const rightDayRect = rightDayEl.getBoundingClientRect()
|
|
232
|
-
const rightEdge = rightDayRect.right - dayColumnsRect.left
|
|
233
|
-
|
|
234
283
|
dragState.value.endDay = targetDayIndex
|
|
235
284
|
dragState.value.end = {
|
|
236
|
-
x:
|
|
237
|
-
y: e.clientY -
|
|
285
|
+
x: dragState.value.start!.x, // Keep x position bound to starting day column
|
|
286
|
+
y: e.clientY - dayColumnsContainer.value.getBoundingClientRect().top
|
|
238
287
|
}
|
|
239
288
|
|
|
240
|
-
// Ensure the selection spans the full width of all selected days
|
|
241
|
-
dragState.value.start.x = leftEdge
|
|
242
|
-
|
|
243
289
|
if (targetDayIndex >= 0 && targetDayIndex < weekDays.value.length) {
|
|
244
|
-
//
|
|
245
|
-
const
|
|
246
|
-
|
|
290
|
+
// Always use the starting day as the base date for the event
|
|
291
|
+
const startDay = new Date(weekDays.value[dragState.value.startDay])
|
|
292
|
+
startDay.setHours(0, 0, 0, 0)
|
|
247
293
|
|
|
248
|
-
|
|
294
|
+
// Calculate end time based on mouse position but on the starting day
|
|
295
|
+
// Use the consistent positioning function for perfect alignment
|
|
296
|
+
const endTime = getTimeFromPositionConsistent(e.clientY, startDay)
|
|
249
297
|
dragState.value.endTime = endTime
|
|
250
298
|
}
|
|
251
299
|
}
|
|
252
300
|
|
|
253
301
|
function handleMouseUp() {
|
|
254
|
-
if (!dragState.value.isDragging || !dragState.value.startTime || !dragState.value.endTime) return
|
|
302
|
+
if (!isDragToCreateEnabled.value || !dragState.value.isDragging || !dragState.value.startTime || !dragState.value.endTime) return
|
|
255
303
|
|
|
256
304
|
let start = new Date(dragState.value.startTime)
|
|
257
305
|
let end = new Date(dragState.value.endTime)
|
|
258
306
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
307
|
+
// Always use the starting day for the event, but allow time selection across days
|
|
308
|
+
// The event will be created on the starting day with the selected time range
|
|
309
|
+
if (end < start) {
|
|
310
|
+
// If end time is before start time, swap them but keep the starting day
|
|
262
311
|
[start, end] = [end, start]
|
|
263
312
|
}
|
|
264
313
|
|
|
@@ -279,21 +328,22 @@ function handleMouseUp() {
|
|
|
279
328
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
280
329
|
}
|
|
281
330
|
|
|
282
|
-
function
|
|
283
|
-
|
|
331
|
+
// Consistent time positioning function that uses the same coordinate system as events
|
|
332
|
+
function getTimeFromPositionConsistent(y: number, day: Date): Date {
|
|
333
|
+
if (!dayColumnsContainer.value) return new Date(day)
|
|
284
334
|
|
|
285
|
-
const
|
|
335
|
+
const containerRect = dayColumnsContainer.value.getBoundingClientRect()
|
|
286
336
|
|
|
287
|
-
// Calculate relative Y position
|
|
288
|
-
const relativeY = y -
|
|
337
|
+
// Calculate relative Y position from the container (same as events use)
|
|
338
|
+
const relativeY = y - containerRect.top
|
|
289
339
|
|
|
290
|
-
// Use the same formula as
|
|
340
|
+
// Use the exact same formula as events
|
|
291
341
|
const minutesPerSlot = slotDuration
|
|
292
342
|
const slotsFromTop = relativeY / slotHeight
|
|
293
343
|
const minutes = Math.floor(slotsFromTop * minutesPerSlot)
|
|
294
344
|
|
|
295
345
|
const date = new Date(day)
|
|
296
|
-
date.setHours(Math.floor(minutes / 60)
|
|
346
|
+
date.setHours(Math.floor(minutes / 60))
|
|
297
347
|
const roundedMinutes = Math.round((minutes % 60) / 5) * 5
|
|
298
348
|
date.setMinutes(roundedMinutes)
|
|
299
349
|
date.setSeconds(0)
|
|
@@ -371,14 +421,10 @@ onUnmounted(() => {
|
|
|
371
421
|
<div class="w-100p overflow-hidden m_overflow h-100p grid">
|
|
372
422
|
<div ref="calendarGrid" class="weekGrid border-bottom me-1">
|
|
373
423
|
<div />
|
|
374
|
-
<div
|
|
375
|
-
v-for="day in weekDays" :key="day.toISOString()"
|
|
376
|
-
class="day-header p-05 txt-center"
|
|
377
|
-
>
|
|
424
|
+
<div v-for="day in weekDays" :key="day.toISOString()" class="day-header p-05 txt-center">
|
|
378
425
|
{{ formatDate(day, { fmt: 'DDD' }) }}
|
|
379
426
|
<span
|
|
380
|
-
class="txt-12 round p-025"
|
|
381
|
-
:class="{
|
|
427
|
+
class="txt-12 round p-025" :class="{
|
|
382
428
|
'color-gray': day.toDateString() !== new Date().toDateString(),
|
|
383
429
|
'bg-primary color-white': day.toDateString() === new Date().toDateString(),
|
|
384
430
|
}"
|
|
@@ -390,28 +436,31 @@ onUnmounted(() => {
|
|
|
390
436
|
<div class="overflow h-100p pe-05">
|
|
391
437
|
<div ref="dayColumnsContainer" class="weekGrid border-end relative">
|
|
392
438
|
<div ref="timeSlotsContainer">
|
|
393
|
-
<div
|
|
439
|
+
<div
|
|
440
|
+
v-for="slot in timeSlots" :key="slot.time"
|
|
441
|
+
class="txt-light txt-12 color-gray flex justify-content-center bg-gray-light border-start"
|
|
442
|
+
:style="{ height: `${slotHeight}px` }"
|
|
443
|
+
>
|
|
394
444
|
{{ slot.time }}
|
|
395
445
|
</div>
|
|
396
446
|
</div>
|
|
397
447
|
|
|
398
448
|
<!-- Current time indicator - ensure it aligns with the time slots and events -->
|
|
399
|
-
<div
|
|
449
|
+
<div
|
|
450
|
+
v-if="isToday" class="absolute end w-100p z-2 flex pointer-events-none ps-4-5"
|
|
451
|
+
:style="{ top: `${currentTimeTop}px` }"
|
|
452
|
+
>
|
|
400
453
|
<div class="current-time-dot h-10px aspect-ratio-1 round bg-primary" />
|
|
401
454
|
<div class="current-time-line w-100p bg-primary" />
|
|
402
455
|
</div>
|
|
403
456
|
|
|
404
457
|
<div
|
|
405
|
-
v-for="(day, index) in weekDays"
|
|
406
|
-
|
|
407
|
-
:ref="el => setDayColumnRef(el, index)"
|
|
408
|
-
class="day-column top bottom border-start relative"
|
|
409
|
-
@mousedown="handleMouseDown($event, day)"
|
|
458
|
+
v-for="(day, index) in weekDays" :key="day.toISOString()" :ref="el => setDayColumnRef(el, index)"
|
|
459
|
+
class="day-column top bottom border-start relative" @mousedown="handleMouseDown($event, day)"
|
|
410
460
|
>
|
|
411
461
|
<template
|
|
412
462
|
v-for="event in processedEvents.filter(e =>
|
|
413
|
-
new Date(e.start_time).toDateString() === day.toDateString())"
|
|
414
|
-
:key="event.id"
|
|
463
|
+
new Date(e.start_time).toDateString() === day.toDateString())" :key="event.id"
|
|
415
464
|
>
|
|
416
465
|
<div
|
|
417
466
|
class="event absolute radius-05 overflow-hidden pointer transition z-1 opacity-8 border"
|
|
@@ -421,16 +470,15 @@ onUnmounted(() => {
|
|
|
421
470
|
left: `${event.left}%`,
|
|
422
471
|
width: `${event.width}%`,
|
|
423
472
|
backgroundColor: event.color || 'var(--bgl-primary)',
|
|
424
|
-
}"
|
|
425
|
-
@mousedown.stop
|
|
426
|
-
@click.stop="handleEventSelection(event, $event)"
|
|
473
|
+
}" @mousedown.stop @click.stop="handleEventSelection(event, $event)"
|
|
427
474
|
>
|
|
428
475
|
<div class="overflow-hidden color-white p-025 txt12 h-100p">
|
|
429
476
|
<div class="white-space ellipsis-1">
|
|
430
477
|
{{ event.title }}
|
|
431
478
|
</div>
|
|
432
479
|
<div class="txt10 opacity-8 user-select-none">
|
|
433
|
-
{{ formatDate(event.start_time, { fmt: 'HH:mm' }) }} - {{ formatDate(event.end_time,
|
|
480
|
+
{{ formatDate(event.start_time, { fmt: 'HH:mm' }) }} - {{ formatDate(event.end_time,
|
|
481
|
+
{ fmt: 'HH:mm' }) }}
|
|
434
482
|
</div>
|
|
435
483
|
</div>
|
|
436
484
|
</div>
|
|
@@ -439,17 +487,19 @@ onUnmounted(() => {
|
|
|
439
487
|
|
|
440
488
|
<!-- Drag selection preview - ensure it aligns with time slots and events -->
|
|
441
489
|
<div
|
|
442
|
-
v-if="dragState.isDragging && dragState.start && dragState.end"
|
|
490
|
+
v-if="isDragToCreateEnabled && dragState.isDragging && dragState.start && dragState.end && dragPreviewPosition"
|
|
443
491
|
class="drag-preview absolute bg-primary-tint pointer-events-none overflow-hidden opacity-7 z-1"
|
|
444
492
|
:style="{
|
|
445
|
-
left: `${
|
|
446
|
-
top: `${
|
|
447
|
-
width: `${
|
|
448
|
-
height: `${
|
|
493
|
+
left: `${dragState.start.x}px`,
|
|
494
|
+
top: `${dragPreviewPosition.top}px`,
|
|
495
|
+
width: `${dayColumns[dragState.startDay]?.offsetWidth || 100}px`,
|
|
496
|
+
height: `${dragPreviewPosition.height}px`,
|
|
449
497
|
}"
|
|
450
498
|
>
|
|
451
499
|
<div class="color-primary txt-12 p-025">
|
|
452
|
-
{{ dragState.startTime ? formatDate(dragState.startTime, { fmt: 'HH:mm' }) : '' }} - {{
|
|
500
|
+
{{ dragState.startTime ? formatDate(dragState.startTime, { fmt: 'HH:mm' }) : '' }} - {{
|
|
501
|
+
dragState.endTime
|
|
502
|
+
? formatDate(dragState.endTime, { fmt: 'HH:mm' }) : '' }}
|
|
453
503
|
</div>
|
|
454
504
|
</div>
|
|
455
505
|
</div>
|
|
@@ -471,8 +521,10 @@ onUnmounted(() => {
|
|
|
471
521
|
|
|
472
522
|
.day-column {
|
|
473
523
|
flex: 1;
|
|
474
|
-
position: relative;
|
|
475
|
-
|
|
524
|
+
position: relative;
|
|
525
|
+
/* Ensure absolute positioning works properly */
|
|
526
|
+
min-height: 100%;
|
|
527
|
+
/* Ensure column fills the full height */
|
|
476
528
|
}
|
|
477
529
|
|
|
478
530
|
/* Add consistent styling for the time slots */
|
|
@@ -496,7 +548,8 @@ onUnmounted(() => {
|
|
|
496
548
|
.drag-preview {
|
|
497
549
|
border: 1px solid var(--bgl-primary);
|
|
498
550
|
background-color: rgba(var(--bgl-primary-rgb), 0.1);
|
|
499
|
-
box-sizing: border-box;
|
|
551
|
+
box-sizing: border-box;
|
|
552
|
+
/* Ensure consistent sizing */
|
|
500
553
|
}
|
|
501
554
|
|
|
502
555
|
.current-time-line {
|
|
@@ -506,7 +559,8 @@ onUnmounted(() => {
|
|
|
506
559
|
|
|
507
560
|
.current-time-dot {
|
|
508
561
|
background-color: var(--bgl-primary);
|
|
509
|
-
margin-right: -5px;
|
|
562
|
+
margin-right: -5px;
|
|
563
|
+
/* Adjust dot position */
|
|
510
564
|
}
|
|
511
565
|
|
|
512
566
|
/* This ensures all grid content aligns properly */
|
|
@@ -79,12 +79,11 @@ const {
|
|
|
79
79
|
})
|
|
80
80
|
|
|
81
81
|
function renderFieldForRow(field: Field<T>, row: T) {
|
|
82
|
-
const { renderField } = useSchemaField<T
|
|
82
|
+
const { renderField } = useSchemaField<T>({
|
|
83
83
|
mode: 'table',
|
|
84
84
|
getFormData: () => row,
|
|
85
|
-
onUpdateModelValue: undefined
|
|
86
85
|
})
|
|
87
|
-
return renderField({ ...field, label: '' } as BaseBagelField<T,
|
|
86
|
+
return renderField({ ...field, label: '' } as BaseBagelField<T, Path<T>>, slots as unknown as BaseBagelField<T, Path<T>>['slots'])
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
const computedItemHeight = $computed(() => `${itemHeight.value}px`)
|