822acf7612
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
333 lines
8.9 KiB
Vue
333 lines
8.9 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, watch } from 'vue';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useRoute } from 'vue-router';
|
|
import { useModuleStore } from '@KTXC/stores/moduleStore';
|
|
import { useChronoOperationsStore } from '@/stores/chronoOperationsStore';
|
|
import { useChronoSettingsStore } from '@/stores/chronoSettingsStore';
|
|
import { useChronoUiStore } from '@/stores/chronoUiStore';
|
|
import type { CalendarView as CalendarViewType } from '@/types';
|
|
import { spanToDays, type AgendaViewSpan, type DaysViewSpan } from '@/types/spans';
|
|
import { daysVisibleRange, monthGridVisibleRange } from '@/utils/date';
|
|
import Sidebar from '@/components/Sidebar.vue';
|
|
import CollectionEditor from '@/components/CollectionEditor.vue';
|
|
import CalendarView from '@/components/CalendarView.vue';
|
|
import TaskView from '@/components/TaskView.vue';
|
|
import EventEditor from '@/components/EventEditor.vue';
|
|
import TaskEditor from '@/components/TaskEditor.vue';
|
|
import ImportDialog from '@/components/ImportDialog.vue';
|
|
import { EpochSpan } from '@/types/date';
|
|
|
|
// Check if chrono manager is available
|
|
const moduleStore = useModuleStore();
|
|
const isChronoManagerAvailable = computed(() => {
|
|
return moduleStore.has('chrono_manager') || moduleStore.has('ChronoManager')
|
|
});
|
|
|
|
const chronoOperationsStore = useChronoOperationsStore();
|
|
const chronoSettingsStore = useChronoSettingsStore();
|
|
const chronoUiStore = useChronoUiStore();
|
|
const route = useRoute();
|
|
|
|
const {
|
|
calendarView,
|
|
daysViewSpan,
|
|
agendaViewSpan,
|
|
} = storeToRefs(chronoSettingsStore);
|
|
|
|
const {
|
|
loading,
|
|
calendars,
|
|
taskLists,
|
|
filteredTasks,
|
|
collections,
|
|
} = storeToRefs(chronoOperationsStore);
|
|
|
|
const {
|
|
currentDate,
|
|
sidebarVisible,
|
|
selectedCollection,
|
|
showEventEditor,
|
|
showTaskEditor,
|
|
showCollectionEditor,
|
|
showImportDialog,
|
|
selectedEntity,
|
|
entityEditorMode,
|
|
editingCollection,
|
|
collectionEditorMode,
|
|
collectionEditorType,
|
|
isTasksSection,
|
|
} = storeToRefs(chronoUiStore);
|
|
|
|
const { loadVisibleRange: loadVisibleRangeOperations } = chronoOperationsStore;
|
|
|
|
const {
|
|
initialize,
|
|
selectSection,
|
|
selectDate,
|
|
createEventFromDate,
|
|
startEditingSelectedEntity,
|
|
cancelEditingSelectedEntity,
|
|
closeEntityEditor,
|
|
editEvent,
|
|
editTask,
|
|
saveEvent,
|
|
deleteEvent,
|
|
saveTask,
|
|
deleteTask,
|
|
saveCollection,
|
|
deleteCollection,
|
|
closeImport,
|
|
} = chronoUiStore;
|
|
|
|
const {
|
|
toggleTaskComplete,
|
|
ensureTasksLoaded,
|
|
} = chronoOperationsStore;
|
|
|
|
watch(() => route.fullPath, async () => {
|
|
selectSectionFromRoute();
|
|
if (isTasksSection.value) {
|
|
await ensureTasksLoaded();
|
|
}
|
|
});
|
|
|
|
watch(
|
|
[calendarView, currentDate, daysViewSpan, agendaViewSpan],
|
|
() => { loadVisibleRangeOperations(computeVisibleRange()); },
|
|
{ immediate: true },
|
|
);
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
selectSectionFromRoute();
|
|
await initialize(isTasksSection.value ? 'tasks' : 'calendar');
|
|
} catch (error) {
|
|
console.error('[Chrono] - Failed to load data from Chrono Manager:', error);
|
|
}
|
|
});
|
|
|
|
function handleCalendarViewChange(view: CalendarViewType) {
|
|
chronoSettingsStore.calendarView = view;
|
|
}
|
|
|
|
function handleCalendarDaysViewSpanChange(span: DaysViewSpan) {
|
|
chronoSettingsStore.daysViewSpan = span;
|
|
}
|
|
|
|
function handleCalendarAgendaViewSpanChange(span: AgendaViewSpan) {
|
|
chronoSettingsStore.agendaViewSpan = span;
|
|
}
|
|
|
|
function computeVisibleRange(): EpochSpan {
|
|
if (calendarView.value === 'month') {
|
|
return monthGridVisibleRange(currentDate.value);
|
|
}
|
|
|
|
const span = calendarView.value === 'agenda'
|
|
? agendaViewSpan.value
|
|
: daysViewSpan.value;
|
|
|
|
return daysVisibleRange(currentDate.value, spanToDays(span));
|
|
}
|
|
|
|
function selectSectionFromRoute() {
|
|
const routePath = route.path;
|
|
const section = routePath.endsWith('/tasks') ? 'tasks' : 'calendar';
|
|
selectSection(section);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="chrono-container">
|
|
<!-- Top Navigation Bar -->
|
|
<v-toolbar elevation="0" density="compact" class="chrono-toolbar">
|
|
<template #prepend>
|
|
<v-btn
|
|
icon="mdi-menu"
|
|
variant="text"
|
|
@click="sidebarVisible = !sidebarVisible"
|
|
></v-btn>
|
|
</template>
|
|
|
|
<v-toolbar-title class="d-flex align-center">
|
|
<v-icon size="24" color="primary" class="mr-2">mdi-calendar-month</v-icon>
|
|
<span class="text-subtitle-1 font-weight-bold">Chrono</span>
|
|
</v-toolbar-title>
|
|
|
|
<v-spacer></v-spacer>
|
|
|
|
<!-- View Switcher for Calendar -->
|
|
<v-btn-toggle
|
|
v-if="!isTasksSection"
|
|
:model-value="calendarView"
|
|
color="primary"
|
|
variant="outlined"
|
|
density="compact"
|
|
mandatory
|
|
class="mr-2"
|
|
@update:model-value="handleCalendarViewChange"
|
|
>
|
|
<v-btn value="days" size="small">Days</v-btn>
|
|
<v-btn value="month" size="small">Month</v-btn>
|
|
<v-btn value="agenda" size="small">Agenda</v-btn>
|
|
</v-btn-toggle>
|
|
</v-toolbar>
|
|
|
|
<!-- Main Content Area -->
|
|
<div class="chrono-content">
|
|
<!-- Sidebar Navigation Drawer -->
|
|
<Sidebar />
|
|
|
|
<!-- Main Calendar/Task View -->
|
|
<div class="chrono-main">
|
|
<v-alert
|
|
v-if="!isChronoManagerAvailable"
|
|
type="warning"
|
|
variant="tonal"
|
|
closable
|
|
class="mb-4"
|
|
>
|
|
<v-alert-title class="d-flex align-center">
|
|
<v-icon icon="mdi-alert-circle" class="mr-2" />
|
|
Chrono Manager Not Available
|
|
</v-alert-title>
|
|
<div class="mt-2">
|
|
<p>
|
|
The Chrono Manager module is not installed or enabled.
|
|
This module requires the <strong>chrono_manager</strong> module to function properly.
|
|
</p>
|
|
<p class="mt-2 mb-0">
|
|
Please contact your system administrator to install and enable the
|
|
<code>chrono_manager</code> module.
|
|
</p>
|
|
</div>
|
|
</v-alert>
|
|
|
|
<div v-if="isChronoManagerAvailable" class="chrono-wrapper">
|
|
<v-progress-linear v-if="loading" indeterminate color="primary" />
|
|
|
|
<CalendarView
|
|
v-if="!isTasksSection"
|
|
:view="calendarView"
|
|
:current-date="currentDate"
|
|
:calendars="calendars"
|
|
:initial-days-span="daysViewSpan"
|
|
:initial-agenda-view-span="agendaViewSpan"
|
|
@event-click="editEvent"
|
|
@date-click="createEventFromDate"
|
|
@date-range-select="createEventFromDate($event.start, $event.end)"
|
|
@update:current-date="selectDate"
|
|
@update:days-span="handleCalendarDaysViewSpanChange"
|
|
@update:agenda-span="handleCalendarAgendaViewSpanChange"
|
|
/>
|
|
|
|
<TaskView
|
|
v-else
|
|
:tasks="filteredTasks"
|
|
:lists="taskLists"
|
|
@task-click="editTask"
|
|
@toggle-complete="toggleTaskComplete"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Event Editor Dialog -->
|
|
<v-dialog v-model="showEventEditor" max-width="800px" scrollable>
|
|
<EventEditor
|
|
v-if="showEventEditor"
|
|
:mode="entityEditorMode"
|
|
:entity="selectedEntity"
|
|
:collection="selectedCollection"
|
|
:calendars="calendars"
|
|
@save="saveEvent"
|
|
@delete="deleteEvent"
|
|
@edit="startEditingSelectedEntity"
|
|
@cancel="cancelEditingSelectedEntity"
|
|
@close="closeEntityEditor"
|
|
/>
|
|
</v-dialog>
|
|
|
|
<!-- Task Editor Dialog -->
|
|
<v-dialog v-model="showTaskEditor" max-width="700px" scrollable>
|
|
<TaskEditor
|
|
v-if="showTaskEditor"
|
|
:mode="entityEditorMode"
|
|
:entity="selectedEntity"
|
|
:collection="selectedCollection"
|
|
:lists="taskLists"
|
|
@save="saveTask"
|
|
@delete="deleteTask"
|
|
@edit="startEditingSelectedEntity"
|
|
@cancel="cancelEditingSelectedEntity"
|
|
@close="closeEntityEditor"
|
|
/>
|
|
</v-dialog>
|
|
|
|
<!-- Collection Editor Dialog -->
|
|
<CollectionEditor
|
|
v-model="showCollectionEditor"
|
|
:collection="editingCollection"
|
|
:mode="collectionEditorMode"
|
|
:type="collectionEditorType"
|
|
@save="saveCollection"
|
|
@delete="deleteCollection"
|
|
/>
|
|
|
|
<ImportDialog v-model="showImportDialog" :collections="collections" @close="closeImport" />
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.chrono-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 0;
|
|
isolation: isolate; /* Create stacking context to prevent style leakage */
|
|
}
|
|
|
|
.chrono-toolbar {
|
|
flex-shrink: 0;
|
|
border-bottom: 1px solid rgb(var(--v-border-color)) !important;
|
|
}
|
|
|
|
.chrono-content {
|
|
display: flex;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.chrono-main {
|
|
flex: 1;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.chrono-wrapper {
|
|
display: flex;
|
|
flex: 1;
|
|
min-height: 0;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 960px) {
|
|
.chrono-main {
|
|
padding: 16px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.chrono-main {
|
|
padding: 12px;
|
|
}
|
|
}
|
|
</style>
|