Initial commit
This commit is contained in:
172
src/components/CollectionList.vue
Normal file
172
src/components/CollectionList.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useCollectionsStore } from '@ChronoManager/stores/collectionsStore'
|
||||
import { CollectionObject } from '@ChronoManager/models/collection';
|
||||
|
||||
// Store
|
||||
const collectionsStore = useCollectionsStore()
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
selectedCollection?: CollectionObject | null
|
||||
type?: 'calendar' | 'tasklist'
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'select': [collection: CollectionObject]
|
||||
'edit': [collection: CollectionObject]
|
||||
'toggle-visibility': [collection: CollectionObject]
|
||||
}>()
|
||||
|
||||
// State
|
||||
const loading = ref(false)
|
||||
const collections = ref<CollectionObject[]>([])
|
||||
|
||||
// Computed
|
||||
const filteredCollections = computed(() => {
|
||||
if (!props.type) return collections.value
|
||||
|
||||
return collections.value.filter(collection => {
|
||||
if (props.type === 'calendar') {
|
||||
return collection.contents?.event
|
||||
} else if (props.type === 'tasklist') {
|
||||
return collection.contents?.task
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const displayTitle = computed(() => {
|
||||
if (props.type === 'calendar') return 'Calendars'
|
||||
if (props.type === 'tasklist') return 'Task Lists'
|
||||
return 'Collections'
|
||||
})
|
||||
|
||||
const displayIcon = computed(() => {
|
||||
if (props.type === 'calendar') return 'mdi-calendar'
|
||||
if (props.type === 'tasklist') return 'mdi-checkbox-marked-outline'
|
||||
return 'mdi-folder'
|
||||
})
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
collections.value = await collectionsStore.list()
|
||||
} catch (error) {
|
||||
console.error('[Chrono] - Failed to load collections:', error)
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
// Functions
|
||||
const onCollectionSelect = (collection: CollectionObject) => {
|
||||
console.log('[Chrono] - Collection selected', collection)
|
||||
emit('select', collection)
|
||||
}
|
||||
|
||||
const onCollectionEdit = (collection: CollectionObject) => {
|
||||
emit('edit', collection)
|
||||
}
|
||||
|
||||
const onToggleVisibility = (collection: CollectionObject) => {
|
||||
collection.enabled = !collection.enabled
|
||||
emit('toggle-visibility', collection)
|
||||
}
|
||||
|
||||
// Expose refresh method
|
||||
defineExpose({
|
||||
async refresh() {
|
||||
loading.value = true
|
||||
try {
|
||||
collections.value = await collectionsStore.list()
|
||||
} catch (error) {
|
||||
console.error('[Chrono] - Failed to load collections:', error)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="collection-selector">
|
||||
<div class="collection-selector-header">
|
||||
<div class="d-flex align-center mb-3">
|
||||
<v-icon :icon="displayIcon" size="small" class="mr-2" />
|
||||
<span class="text-subtitle-2 font-weight-bold">{{ displayTitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<div class="collection-selector-content">
|
||||
<v-progress-linear v-if="loading" indeterminate color="primary" />
|
||||
|
||||
<v-list v-else density="compact" nav class="pa-0">
|
||||
<v-list-item
|
||||
v-for="collection in filteredCollections"
|
||||
:key="collection.id"
|
||||
:value="collection.id"
|
||||
:active="selectedCollection?.id === collection.id"
|
||||
@click="onCollectionSelect(collection)"
|
||||
rounded="lg"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-checkbox-btn
|
||||
:model-value="collection.enabled !== false"
|
||||
:color="collection.color || 'primary'"
|
||||
hide-details
|
||||
@click.stop="onToggleVisibility(collection)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item-title>{{ collection.label || 'Unnamed Collection' }}</v-list-item-title>
|
||||
|
||||
<template #append>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon
|
||||
:color="collection.color || 'primary'"
|
||||
size="small"
|
||||
class="mr-1"
|
||||
>mdi-circle</v-icon>
|
||||
<v-btn
|
||||
icon="mdi-pencil"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
@click.stop="onCollectionEdit(collection)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<v-alert v-if="!loading && filteredCollections.length === 0" type="info" variant="tonal" density="compact" class="mt-2">
|
||||
No {{ displayTitle.toLowerCase() }} found
|
||||
</v-alert>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.collection-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.collection-selector-header {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.collection-selector-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.v-list-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user