Update calendar localization and enhance CalendarView: Add new entries, edit options, selection info, and form fields in both English and German localization files. Improve CalendarView component with new event dialog and selection handling for a better user experience.
This commit is contained in:
@@ -3,6 +3,11 @@
|
|||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Kalender",
|
"title": "Kalender",
|
||||||
"today": "Heute",
|
"today": "Heute",
|
||||||
|
"newEntry": "Neuer Eintrag",
|
||||||
|
"editEntry": "Eintrag bearbeiten",
|
||||||
|
"selectedDays": "{count} Tage ausgewählt",
|
||||||
|
"createEventForSelection": "Termin erstellen",
|
||||||
|
"clearSelection": "Auswahl aufheben",
|
||||||
"views": {
|
"views": {
|
||||||
"month": "Monat",
|
"month": "Monat",
|
||||||
"week": "Woche",
|
"week": "Woche",
|
||||||
@@ -40,6 +45,31 @@
|
|||||||
"oct": "Oktober",
|
"oct": "Oktober",
|
||||||
"nov": "November",
|
"nov": "November",
|
||||||
"dec": "Dezember"
|
"dec": "Dezember"
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"personal": "Persönlich",
|
||||||
|
"work": "Arbeit",
|
||||||
|
"family": "Familie",
|
||||||
|
"health": "Gesundheit",
|
||||||
|
"birthday": "Geburtstag",
|
||||||
|
"holiday": "Urlaub",
|
||||||
|
"reminder": "Erinnerung",
|
||||||
|
"other": "Sonstiges"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"title": "Titel",
|
||||||
|
"titlePlaceholder": "Titel eingeben...",
|
||||||
|
"category": "Kategorie",
|
||||||
|
"startDate": "Startdatum",
|
||||||
|
"startTime": "Startzeit",
|
||||||
|
"endDate": "Enddatum",
|
||||||
|
"endTime": "Endzeit",
|
||||||
|
"allDay": "Ganztägig",
|
||||||
|
"description": "Beschreibung",
|
||||||
|
"descriptionPlaceholder": "Optionale Beschreibung...",
|
||||||
|
"save": "Speichern",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"delete": "Löschen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
"calendar": {
|
"calendar": {
|
||||||
"title": "Calendar",
|
"title": "Calendar",
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
|
"newEntry": "New Entry",
|
||||||
|
"editEntry": "Edit Entry",
|
||||||
|
"selectedDays": "{count} days selected",
|
||||||
|
"createEventForSelection": "Create Event",
|
||||||
|
"clearSelection": "Clear Selection",
|
||||||
"views": {
|
"views": {
|
||||||
"month": "Month",
|
"month": "Month",
|
||||||
"week": "Week",
|
"week": "Week",
|
||||||
@@ -40,6 +45,31 @@
|
|||||||
"oct": "October",
|
"oct": "October",
|
||||||
"nov": "November",
|
"nov": "November",
|
||||||
"dec": "December"
|
"dec": "December"
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"personal": "Personal",
|
||||||
|
"work": "Work",
|
||||||
|
"family": "Family",
|
||||||
|
"health": "Health",
|
||||||
|
"birthday": "Birthday",
|
||||||
|
"holiday": "Holiday",
|
||||||
|
"reminder": "Reminder",
|
||||||
|
"other": "Other"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"title": "Title",
|
||||||
|
"titlePlaceholder": "Enter title...",
|
||||||
|
"category": "Category",
|
||||||
|
"startDate": "Start Date",
|
||||||
|
"startTime": "Start Time",
|
||||||
|
"endDate": "End Date",
|
||||||
|
"endTime": "End Time",
|
||||||
|
"allDay": "All Day",
|
||||||
|
"description": "Description",
|
||||||
|
"descriptionPlaceholder": "Optional description...",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete": "Delete"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
<!-- Toolbar -->
|
<!-- Toolbar -->
|
||||||
<div class="calendar-toolbar">
|
<div class="calendar-toolbar">
|
||||||
<div class="nav-buttons">
|
<div class="nav-buttons">
|
||||||
|
<button @click="openNewEventDialog()" class="btn-new-event">
|
||||||
|
+ {{ $t('personal.calendar.newEntry') }}
|
||||||
|
</button>
|
||||||
<button @click="goToToday" class="btn-today">{{ $t('personal.calendar.today') }}</button>
|
<button @click="goToToday" class="btn-today">{{ $t('personal.calendar.today') }}</button>
|
||||||
<button @click="navigatePrev" class="btn-nav"><</button>
|
<button @click="navigatePrev" class="btn-nav"><</button>
|
||||||
<button @click="navigateNext" class="btn-nav">></button>
|
<button @click="navigateNext" class="btn-nav">></button>
|
||||||
@@ -23,6 +26,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Selection info -->
|
||||||
|
<div v-if="selectedDates.length > 1" class="selection-info">
|
||||||
|
{{ $t('personal.calendar.selectedDays', { count: selectedDates.length }) }}
|
||||||
|
<button @click="createEventFromSelection" class="btn-create-from-selection">
|
||||||
|
{{ $t('personal.calendar.createEventForSelection') }}
|
||||||
|
</button>
|
||||||
|
<button @click="clearSelection" class="btn-clear-selection">
|
||||||
|
{{ $t('personal.calendar.clearSelection') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Month View -->
|
<!-- Month View -->
|
||||||
<div v-if="currentView === 'month'" class="calendar-grid month-view">
|
<div v-if="currentView === 'month'" class="calendar-grid month-view">
|
||||||
<div class="weekday-headers">
|
<div class="weekday-headers">
|
||||||
@@ -37,19 +51,26 @@
|
|||||||
:class="['day-cell', {
|
:class="['day-cell', {
|
||||||
'other-month': !day.currentMonth,
|
'other-month': !day.currentMonth,
|
||||||
'today': day.isToday,
|
'today': day.isToday,
|
||||||
'weekend': day.isWeekend
|
'weekend': day.isWeekend,
|
||||||
|
'selected': isDateSelected(day.date)
|
||||||
}]"
|
}]"
|
||||||
@click="selectDate(day.date)"
|
@click="handleDayClick(day.date, $event)"
|
||||||
|
@mousedown="startDragSelection(day.date)"
|
||||||
|
@mouseenter="continueDragSelection(day.date)"
|
||||||
|
@mouseup="endDragSelection"
|
||||||
>
|
>
|
||||||
<span class="day-number">{{ day.dayNumber }}</span>
|
<span class="day-number">{{ day.dayNumber }}</span>
|
||||||
<div class="day-events">
|
<div class="day-events">
|
||||||
<div
|
<div
|
||||||
v-for="event in getEventsForDate(day.date)"
|
v-for="event in getEventsForDate(day.date)"
|
||||||
:key="event.id"
|
:key="event.id"
|
||||||
class="event-dot"
|
class="event-item"
|
||||||
:style="{ backgroundColor: event.color }"
|
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
|
||||||
:title="event.title"
|
:title="event.title"
|
||||||
></div>
|
@click.stop="editEvent(event)"
|
||||||
|
>
|
||||||
|
{{ event.title }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,8 +99,18 @@
|
|||||||
v-for="hour in hours"
|
v-for="hour in hours"
|
||||||
:key="hour"
|
:key="hour"
|
||||||
:class="['time-slot', { 'current-hour': isCurrentHour(day.date, hour) }]"
|
:class="['time-slot', { 'current-hour': isCurrentHour(day.date, hour) }]"
|
||||||
@click="createEventAt(day.date, hour)"
|
@click="openNewEventDialog(day.date, hour)"
|
||||||
></div>
|
>
|
||||||
|
<div
|
||||||
|
v-for="event in getEventsForDateTime(day.date, hour)"
|
||||||
|
:key="event.id"
|
||||||
|
class="time-event"
|
||||||
|
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
|
||||||
|
@click.stop="editEvent(event)"
|
||||||
|
>
|
||||||
|
{{ event.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,8 +139,18 @@
|
|||||||
v-for="hour in workHours"
|
v-for="hour in workHours"
|
||||||
:key="hour"
|
:key="hour"
|
||||||
:class="['time-slot', { 'current-hour': isCurrentHour(day.date, hour) }]"
|
:class="['time-slot', { 'current-hour': isCurrentHour(day.date, hour) }]"
|
||||||
@click="createEventAt(day.date, hour)"
|
@click="openNewEventDialog(day.date, hour)"
|
||||||
></div>
|
>
|
||||||
|
<div
|
||||||
|
v-for="event in getEventsForDateTime(day.date, hour)"
|
||||||
|
:key="event.id"
|
||||||
|
class="time-event"
|
||||||
|
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
|
||||||
|
@click.stop="editEvent(event)"
|
||||||
|
>
|
||||||
|
{{ event.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +160,7 @@
|
|||||||
<div v-if="currentView === 'day'" class="calendar-grid day-view">
|
<div v-if="currentView === 'day'" class="calendar-grid day-view">
|
||||||
<div class="day-header">
|
<div class="day-header">
|
||||||
<div :class="['day-title', { today: isDayToday }]">
|
<div :class="['day-title', { today: isDayToday }]">
|
||||||
{{ $t(`personal.calendar.weekdays.${currentDayData.weekday}`) }},
|
{{ $t(`personal.calendar.weekdaysFull.${currentDayData.weekday}`) }},
|
||||||
{{ currentDayData.dayNumber }}. {{ $t(`personal.calendar.months.${currentDayData.month}`) }} {{ currentDayData.year }}
|
{{ currentDayData.dayNumber }}. {{ $t(`personal.calendar.months.${currentDayData.month}`) }} {{ currentDayData.year }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,14 +175,115 @@
|
|||||||
<div
|
<div
|
||||||
v-for="hour in hours"
|
v-for="hour in hours"
|
||||||
:key="hour"
|
:key="hour"
|
||||||
:class="['time-slot', { 'current-hour': isCurrentHour(currentDate, hour) }]"
|
:class="['time-slot', { 'current-hour': isCurrentHour(currentDateStr, hour) }]"
|
||||||
@click="createEventAt(currentDate, hour)"
|
@click="openNewEventDialog(currentDateStr, hour)"
|
||||||
></div>
|
>
|
||||||
|
<div
|
||||||
|
v-for="event in getEventsForDateTime(currentDateStr, hour)"
|
||||||
|
:key="event.id"
|
||||||
|
class="time-event full-width"
|
||||||
|
:style="{ backgroundColor: getCategoryColor(event.categoryId) }"
|
||||||
|
@click.stop="editEvent(event)"
|
||||||
|
>
|
||||||
|
<span class="event-time">{{ formatEventTime(event) }}</span>
|
||||||
|
{{ event.title }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Event Dialog -->
|
||||||
|
<div v-if="showEventDialog" class="dialog-overlay" @click.self="closeEventDialog">
|
||||||
|
<div class="event-dialog">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<h3>{{ editingEvent ? $t('personal.calendar.editEntry') : $t('personal.calendar.newEntry') }}</h3>
|
||||||
|
<button @click="closeEventDialog" class="btn-close">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.title') }}</label>
|
||||||
|
<input
|
||||||
|
v-model="eventForm.title"
|
||||||
|
type="text"
|
||||||
|
:placeholder="$t('personal.calendar.form.titlePlaceholder')"
|
||||||
|
ref="eventTitleInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.category') }}</label>
|
||||||
|
<div class="category-selector">
|
||||||
|
<button
|
||||||
|
v-for="cat in categories"
|
||||||
|
:key="cat.id"
|
||||||
|
:class="['category-btn', { selected: eventForm.categoryId === cat.id }]"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: eventForm.categoryId === cat.id ? cat.color : 'transparent',
|
||||||
|
borderColor: cat.color,
|
||||||
|
color: eventForm.categoryId === cat.id ? getContrastColor(cat.color) : cat.color
|
||||||
|
}"
|
||||||
|
@click="eventForm.categoryId = cat.id"
|
||||||
|
>
|
||||||
|
{{ $t(`personal.calendar.categories.${cat.id}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.startDate') }}</label>
|
||||||
|
<input v-model="eventForm.startDate" type="date" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.startTime') }}</label>
|
||||||
|
<input v-model="eventForm.startTime" type="time" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.endDate') }}</label>
|
||||||
|
<input v-model="eventForm.endDate" type="date" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.endTime') }}</label>
|
||||||
|
<input v-model="eventForm.endTime" type="time" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input v-model="eventForm.allDay" type="checkbox" />
|
||||||
|
{{ $t('personal.calendar.form.allDay') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('personal.calendar.form.description') }}</label>
|
||||||
|
<textarea
|
||||||
|
v-model="eventForm.description"
|
||||||
|
rows="3"
|
||||||
|
:placeholder="$t('personal.calendar.form.descriptionPlaceholder')"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button v-if="editingEvent" @click="deleteEvent" class="btn-delete">
|
||||||
|
{{ $t('personal.calendar.form.delete') }}
|
||||||
|
</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<button @click="closeEventDialog" class="btn-cancel">
|
||||||
|
{{ $t('personal.calendar.form.cancel') }}
|
||||||
|
</button>
|
||||||
|
<button @click="saveEvent" class="btn-save" :disabled="!eventForm.title">
|
||||||
|
{{ $t('personal.calendar.form.save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -159,8 +301,42 @@ export default {
|
|||||||
],
|
],
|
||||||
weekDays: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
|
weekDays: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
|
||||||
hours: Array.from({ length: 24 }, (_, i) => i),
|
hours: Array.from({ length: 24 }, (_, i) => i),
|
||||||
workHours: Array.from({ length: 12 }, (_, i) => i + 7), // 7:00 - 18:00
|
workHours: Array.from({ length: 12 }, (_, i) => i + 7),
|
||||||
events: [] // Placeholder for events
|
|
||||||
|
// Categories with colors
|
||||||
|
categories: [
|
||||||
|
{ id: 'personal', color: '#4CAF50' }, // Green
|
||||||
|
{ id: 'work', color: '#2196F3' }, // Blue
|
||||||
|
{ id: 'family', color: '#9C27B0' }, // Purple
|
||||||
|
{ id: 'health', color: '#F44336' }, // Red
|
||||||
|
{ id: 'birthday', color: '#FF9800' }, // Orange
|
||||||
|
{ id: 'holiday', color: '#00BCD4' }, // Cyan
|
||||||
|
{ id: 'reminder', color: '#795548' }, // Brown
|
||||||
|
{ id: 'other', color: '#607D8B' } // Gray-Blue
|
||||||
|
],
|
||||||
|
|
||||||
|
// Events storage
|
||||||
|
events: [],
|
||||||
|
nextEventId: 1,
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
selectedDates: [],
|
||||||
|
isDragging: false,
|
||||||
|
dragStartDate: null,
|
||||||
|
|
||||||
|
// Dialog
|
||||||
|
showEventDialog: false,
|
||||||
|
editingEvent: null,
|
||||||
|
eventForm: {
|
||||||
|
title: '',
|
||||||
|
categoryId: 'personal',
|
||||||
|
startDate: '',
|
||||||
|
startTime: '09:00',
|
||||||
|
endDate: '',
|
||||||
|
endTime: '10:00',
|
||||||
|
allDay: false,
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -171,13 +347,14 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.currentDate.toLocaleDateString(this.$i18n.locale, options);
|
return this.currentDate.toLocaleDateString(this.$i18n.locale, options);
|
||||||
},
|
},
|
||||||
|
currentDateStr() {
|
||||||
|
return this.currentDate.toISOString().split('T')[0];
|
||||||
|
},
|
||||||
monthDays() {
|
monthDays() {
|
||||||
const year = this.currentDate.getFullYear();
|
const year = this.currentDate.getFullYear();
|
||||||
const month = this.currentDate.getMonth();
|
const month = this.currentDate.getMonth();
|
||||||
const firstDay = new Date(year, month, 1);
|
const firstDay = new Date(year, month, 1);
|
||||||
const lastDay = new Date(year, month + 1, 0);
|
|
||||||
|
|
||||||
// Find Monday of the first week
|
|
||||||
let startDate = new Date(firstDay);
|
let startDate = new Date(firstDay);
|
||||||
const dayOfWeek = firstDay.getDay();
|
const dayOfWeek = firstDay.getDay();
|
||||||
const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
||||||
@@ -187,7 +364,6 @@ export default {
|
|||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
// Generate 6 weeks (42 days)
|
|
||||||
for (let i = 0; i < 42; i++) {
|
for (let i = 0; i < 42; i++) {
|
||||||
const date = new Date(startDate);
|
const date = new Date(startDate);
|
||||||
date.setDate(startDate.getDate() + i);
|
date.setDate(startDate.getDate() + i);
|
||||||
@@ -209,7 +385,6 @@ export default {
|
|||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
// Find Monday of current week
|
|
||||||
const monday = this.getMonday(this.currentDate);
|
const monday = this.getMonday(this.currentDate);
|
||||||
|
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
@@ -229,13 +404,11 @@ export default {
|
|||||||
return days;
|
return days;
|
||||||
},
|
},
|
||||||
workWeekDaysData() {
|
workWeekDaysData() {
|
||||||
// Only Monday to Friday
|
|
||||||
return this.weekDaysData.slice(0, 5);
|
return this.weekDaysData.slice(0, 5);
|
||||||
},
|
},
|
||||||
currentDayData() {
|
currentDayData() {
|
||||||
const date = this.currentDate;
|
const date = this.currentDate;
|
||||||
const dayIndex = date.getDay();
|
const dayIndex = date.getDay();
|
||||||
// Convert Sunday (0) to index 6, Monday (1) to 0, etc.
|
|
||||||
const weekdayIndex = dayIndex === 0 ? 6 : dayIndex - 1;
|
const weekdayIndex = dayIndex === 0 ? 6 : dayIndex - 1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -251,6 +424,13 @@ export default {
|
|||||||
return this.currentDate.toDateString() === today.toDateString();
|
return this.currentDate.toDateString() === today.toDateString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadEvents();
|
||||||
|
document.addEventListener('mouseup', this.endDragSelection);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
document.removeEventListener('mouseup', this.endDragSelection);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getMonday(date) {
|
getMonday(date) {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
@@ -305,16 +485,210 @@ export default {
|
|||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
return date.toDateString() === now.toDateString() && now.getHours() === hour;
|
return date.toDateString() === now.toDateString() && now.getHours() === hour;
|
||||||
},
|
},
|
||||||
selectDate(dateStr) {
|
|
||||||
this.currentDate = new Date(dateStr);
|
// Selection methods
|
||||||
this.currentView = 'day';
|
isDateSelected(dateStr) {
|
||||||
|
return this.selectedDates.includes(dateStr);
|
||||||
},
|
},
|
||||||
createEventAt(dateStr, hour) {
|
handleDayClick(dateStr, event) {
|
||||||
// Placeholder for event creation
|
if (event.shiftKey && this.selectedDates.length > 0) {
|
||||||
console.log(`Create event at ${dateStr} ${hour}:00`);
|
// Shift-click: select range
|
||||||
|
const lastSelected = this.selectedDates[this.selectedDates.length - 1];
|
||||||
|
this.selectDateRange(lastSelected, dateStr);
|
||||||
|
} else if (event.ctrlKey || event.metaKey) {
|
||||||
|
// Ctrl/Cmd-click: toggle selection
|
||||||
|
const index = this.selectedDates.indexOf(dateStr);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedDates.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.selectedDates.push(dateStr);
|
||||||
|
}
|
||||||
|
} else if (!this.isDragging) {
|
||||||
|
// Normal click: open event dialog for this day
|
||||||
|
this.openNewEventDialog(dateStr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startDragSelection(dateStr) {
|
||||||
|
this.isDragging = true;
|
||||||
|
this.dragStartDate = dateStr;
|
||||||
|
this.selectedDates = [dateStr];
|
||||||
|
},
|
||||||
|
continueDragSelection(dateStr) {
|
||||||
|
if (this.isDragging && this.dragStartDate) {
|
||||||
|
this.selectDateRange(this.dragStartDate, dateStr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endDragSelection() {
|
||||||
|
if (this.isDragging && this.selectedDates.length === 1) {
|
||||||
|
// Single day was clicked, not dragged
|
||||||
|
this.selectedDates = [];
|
||||||
|
}
|
||||||
|
this.isDragging = false;
|
||||||
|
this.dragStartDate = null;
|
||||||
|
},
|
||||||
|
selectDateRange(startStr, endStr) {
|
||||||
|
const start = new Date(startStr);
|
||||||
|
const end = new Date(endStr);
|
||||||
|
|
||||||
|
if (start > end) {
|
||||||
|
[start, end] = [end, start];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedDates = [];
|
||||||
|
const current = new Date(start);
|
||||||
|
while (current <= end) {
|
||||||
|
this.selectedDates.push(current.toISOString().split('T')[0]);
|
||||||
|
current.setDate(current.getDate() + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearSelection() {
|
||||||
|
this.selectedDates = [];
|
||||||
|
},
|
||||||
|
createEventFromSelection() {
|
||||||
|
if (this.selectedDates.length > 0) {
|
||||||
|
const sorted = [...this.selectedDates].sort();
|
||||||
|
this.openNewEventDialog(sorted[0], null, sorted[sorted.length - 1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Event methods
|
||||||
|
getCategoryColor(categoryId) {
|
||||||
|
const cat = this.categories.find(c => c.id === categoryId);
|
||||||
|
return cat ? cat.color : '#607D8B';
|
||||||
|
},
|
||||||
|
getContrastColor(hexColor) {
|
||||||
|
const r = parseInt(hexColor.slice(1, 3), 16);
|
||||||
|
const g = parseInt(hexColor.slice(3, 5), 16);
|
||||||
|
const b = parseInt(hexColor.slice(5, 7), 16);
|
||||||
|
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||||
|
return luminance > 0.5 ? '#000000' : '#ffffff';
|
||||||
},
|
},
|
||||||
getEventsForDate(dateStr) {
|
getEventsForDate(dateStr) {
|
||||||
return this.events.filter(e => e.date === dateStr);
|
return this.events.filter(event => {
|
||||||
|
const eventStart = event.startDate;
|
||||||
|
const eventEnd = event.endDate || event.startDate;
|
||||||
|
return dateStr >= eventStart && dateStr <= eventEnd;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getEventsForDateTime(dateStr, hour) {
|
||||||
|
return this.events.filter(event => {
|
||||||
|
if (event.allDay) return false;
|
||||||
|
const eventStart = event.startDate;
|
||||||
|
const eventEnd = event.endDate || event.startDate;
|
||||||
|
if (dateStr < eventStart || dateStr > eventEnd) return false;
|
||||||
|
|
||||||
|
const startHour = parseInt(event.startTime?.split(':')[0] || '0');
|
||||||
|
const endHour = parseInt(event.endTime?.split(':')[0] || '23');
|
||||||
|
|
||||||
|
if (dateStr === eventStart && dateStr === eventEnd) {
|
||||||
|
return hour >= startHour && hour < endHour;
|
||||||
|
} else if (dateStr === eventStart) {
|
||||||
|
return hour >= startHour;
|
||||||
|
} else if (dateStr === eventEnd) {
|
||||||
|
return hour < endHour;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatEventTime(event) {
|
||||||
|
if (event.allDay) return '';
|
||||||
|
return event.startTime || '';
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dialog methods
|
||||||
|
openNewEventDialog(dateStr = null, hour = null, endDateStr = null) {
|
||||||
|
this.editingEvent = null;
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
this.eventForm = {
|
||||||
|
title: '',
|
||||||
|
categoryId: 'personal',
|
||||||
|
startDate: dateStr || today,
|
||||||
|
startTime: hour !== null ? `${hour.toString().padStart(2, '0')}:00` : '09:00',
|
||||||
|
endDate: endDateStr || dateStr || today,
|
||||||
|
endTime: hour !== null ? `${(hour + 1).toString().padStart(2, '0')}:00` : '10:00',
|
||||||
|
allDay: endDateStr !== null && endDateStr !== dateStr,
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showEventDialog = true;
|
||||||
|
this.selectedDates = [];
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.eventTitleInput?.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editEvent(event) {
|
||||||
|
this.editingEvent = event;
|
||||||
|
this.eventForm = {
|
||||||
|
title: event.title,
|
||||||
|
categoryId: event.categoryId,
|
||||||
|
startDate: event.startDate,
|
||||||
|
startTime: event.startTime || '09:00',
|
||||||
|
endDate: event.endDate || event.startDate,
|
||||||
|
endTime: event.endTime || '10:00',
|
||||||
|
allDay: event.allDay || false,
|
||||||
|
description: event.description || ''
|
||||||
|
};
|
||||||
|
this.showEventDialog = true;
|
||||||
|
},
|
||||||
|
closeEventDialog() {
|
||||||
|
this.showEventDialog = false;
|
||||||
|
this.editingEvent = null;
|
||||||
|
},
|
||||||
|
saveEvent() {
|
||||||
|
if (!this.eventForm.title) return;
|
||||||
|
|
||||||
|
const eventData = {
|
||||||
|
title: this.eventForm.title,
|
||||||
|
categoryId: this.eventForm.categoryId,
|
||||||
|
startDate: this.eventForm.startDate,
|
||||||
|
startTime: this.eventForm.allDay ? null : this.eventForm.startTime,
|
||||||
|
endDate: this.eventForm.endDate,
|
||||||
|
endTime: this.eventForm.allDay ? null : this.eventForm.endTime,
|
||||||
|
allDay: this.eventForm.allDay,
|
||||||
|
description: this.eventForm.description
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.editingEvent) {
|
||||||
|
// Update existing event
|
||||||
|
const index = this.events.findIndex(e => e.id === this.editingEvent.id);
|
||||||
|
if (index > -1) {
|
||||||
|
this.events[index] = { ...eventData, id: this.editingEvent.id };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create new event
|
||||||
|
this.events.push({ ...eventData, id: this.nextEventId++ });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveEvents();
|
||||||
|
this.closeEventDialog();
|
||||||
|
},
|
||||||
|
deleteEvent() {
|
||||||
|
if (this.editingEvent) {
|
||||||
|
const index = this.events.findIndex(e => e.id === this.editingEvent.id);
|
||||||
|
if (index > -1) {
|
||||||
|
this.events.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.saveEvents();
|
||||||
|
this.closeEventDialog();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Persistence (localStorage for now, later backend)
|
||||||
|
saveEvents() {
|
||||||
|
localStorage.setItem('calendarEvents', JSON.stringify(this.events));
|
||||||
|
localStorage.setItem('calendarNextId', this.nextEventId.toString());
|
||||||
|
},
|
||||||
|
loadEvents() {
|
||||||
|
const saved = localStorage.getItem('calendarEvents');
|
||||||
|
if (saved) {
|
||||||
|
this.events = JSON.parse(saved);
|
||||||
|
}
|
||||||
|
const nextId = localStorage.getItem('calendarNextId');
|
||||||
|
if (nextId) {
|
||||||
|
this.nextEventId = parseInt(nextId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -348,6 +722,20 @@ h2 {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-new-event {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--color-primary-green);
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-primary-green-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn-today {
|
.btn-today {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
background: var(--color-primary-orange);
|
background: var(--color-primary-orange);
|
||||||
@@ -410,12 +798,53 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Selection info bar
|
||||||
|
.selection-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #e3f2fd;
|
||||||
|
border: 1px solid #90caf9;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-create-from-selection {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--color-primary-green);
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-primary-green-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-clear-selection {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #fff;
|
||||||
|
color: #666;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Month View
|
// Month View
|
||||||
.month-view {
|
.month-view {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.weekday-headers {
|
.weekday-headers {
|
||||||
@@ -439,7 +868,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.day-cell {
|
.day-cell {
|
||||||
min-height: 80px;
|
min-height: 90px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-right: 1px solid #eee;
|
border-right: 1px solid #eee;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
@@ -480,6 +909,14 @@ h2 {
|
|||||||
&.weekend {
|
&.weekend {
|
||||||
background: #f9f9f9;
|
background: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: #bbdefb !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #90caf9 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.day-number {
|
.day-number {
|
||||||
@@ -490,15 +927,25 @@ h2 {
|
|||||||
|
|
||||||
.day-events {
|
.day-events {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-dot {
|
.event-item {
|
||||||
width: 8px;
|
padding: 2px 6px;
|
||||||
height: 8px;
|
border-radius: 3px;
|
||||||
border-radius: 50%;
|
font-size: 0.75em;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Week & Day View
|
// Week & Day View
|
||||||
@@ -608,6 +1055,7 @@ h2 {
|
|||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-primary-orange-light);
|
background: var(--color-primary-orange-light);
|
||||||
@@ -619,6 +1067,35 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-event {
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
right: 2px;
|
||||||
|
top: 2px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.75em;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-time {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
// Day View specifics
|
// Day View specifics
|
||||||
.day-header {
|
.day-header {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -636,6 +1113,202 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dialog
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-dialog {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
background: var(--color-primary-orange);
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #000;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-primary-orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-selector {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 2px solid;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #f44336;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #d32f2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--color-primary-green);
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: var(--color-primary-green-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Responsive
|
// Responsive
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.calendar-toolbar {
|
.calendar-toolbar {
|
||||||
@@ -645,6 +1318,7 @@ h2 {
|
|||||||
|
|
||||||
.nav-buttons {
|
.nav-buttons {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-tabs {
|
.view-tabs {
|
||||||
@@ -660,5 +1334,15 @@ h2 {
|
|||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-info {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user