refactor: standardize provider #3
@@ -2,7 +2,7 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useServicesStore } from '@PeopleManager/stores/servicesStore'
|
||||
import { CollectionObject } from '@PeopleManager/models/collection'
|
||||
import { ServiceObject } from '@PeopleManager/models/service'
|
||||
import type { ServiceObject } from '@PeopleManager/models/service'
|
||||
|
||||
// Store
|
||||
const servicesStore = useServicesStore()
|
||||
@@ -51,7 +51,7 @@ const dialogOpen = computed({
|
||||
// Functions
|
||||
const onOpen = async () => {
|
||||
if (services.value.length === 0) {
|
||||
services.value = await servicesStore.list()
|
||||
services.value = Object.values(await servicesStore.list())
|
||||
}
|
||||
|
||||
if (!props.collection) {
|
||||
@@ -61,14 +61,14 @@ const onOpen = async () => {
|
||||
// Clone the collection to avoid mutating the original
|
||||
editingCollection.value = props.collection.clone()
|
||||
|
||||
if (props.collection.id !== null) {
|
||||
if (props.collection.identifier) {
|
||||
// Edit mode - find the service
|
||||
editingCollectionService.value = services.value.find(s =>
|
||||
s.provider === props.collection!.provider && s.id === props.collection!.service
|
||||
s.provider === props.collection!.provider && s.identifier === props.collection!.service
|
||||
) || null
|
||||
} else {
|
||||
// Create mode - use first service that can create
|
||||
editingCollectionService.value = services.value.filter(s => s.capabilities?.CollectionCreate)[0] || null
|
||||
editingCollectionService.value = services.value.filter(s => s.capable('CollectionCreate'))[0] || null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ const onColorSelect = (color: string | null, closeMenu = true) => {
|
||||
if (!editingCollection.value) {
|
||||
return
|
||||
}
|
||||
editingCollection.value.color = color
|
||||
editingCollection.value.properties.color = color
|
||||
if (closeMenu) {
|
||||
colorMenuOpen.value = false
|
||||
}
|
||||
@@ -132,9 +132,9 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
<v-combobox v-show="mode === 'create' && services.length > 1"
|
||||
v-model="editingCollectionService"
|
||||
label="Service"
|
||||
:items="services.filter(s => s.capabilities?.CollectionCreate)"
|
||||
:items="services.filter(s => s.capable('CollectionCreate'))"
|
||||
item-title="label"
|
||||
item-value="id"
|
||||
item-value="identifier"
|
||||
required
|
||||
:rules="[(v: ServiceObject) => !!v || 'Service is required']"
|
||||
/>
|
||||
@@ -142,7 +142,7 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
<div class="mb-4"><strong>Service </strong> {{ editingCollection.service }}</div>
|
||||
|
||||
<v-text-field
|
||||
v-model="editingCollection.label"
|
||||
v-model="editingCollection.properties.label"
|
||||
label="Label"
|
||||
required
|
||||
:rules="[(v: string) => !!v || 'Label is required']"
|
||||
@@ -160,7 +160,7 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
icon
|
||||
variant="text"
|
||||
size="small"
|
||||
:style="{ color: editingCollection.color || 'var(--v-theme-on-surface)' }"
|
||||
:style="{ color: editingCollection.properties.color || 'var(--v-theme-on-surface)' }"
|
||||
aria-label="Select book color"
|
||||
title="Select color"
|
||||
>
|
||||
@@ -192,11 +192,11 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
variant="flat"
|
||||
size="small"
|
||||
class="color-menu-body__presets--swatch"
|
||||
:class="{ 'color-menu-body__presets--swatch--active': editingCollection.color === color }"
|
||||
:class="{ 'color-menu-body__presets--swatch--active': editingCollection.properties.color === color }"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="onColorSelect(color)">
|
||||
<v-icon
|
||||
v-if="editingCollection.color === color"
|
||||
v-if="editingCollection.properties.color === color"
|
||||
icon="mdi-check"
|
||||
size="x-small"
|
||||
color="white"
|
||||
@@ -207,7 +207,7 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
|
||||
<div class="color-menu-body__picker">
|
||||
<v-color-picker
|
||||
v-model="editingCollection.color"
|
||||
v-model="editingCollection.properties.color"
|
||||
mode="hex"
|
||||
hide-canvas
|
||||
width="100%"
|
||||
@@ -221,20 +221,10 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
</v-text-field>
|
||||
|
||||
<v-textarea
|
||||
v-model="editingCollection.description"
|
||||
v-model="editingCollection.properties.description"
|
||||
label="Description"
|
||||
rows="2"
|
||||
/>
|
||||
|
||||
<v-row>
|
||||
<v-col v-if="mode === 'edit'" cols="6">
|
||||
<v-switch
|
||||
v-model="editingCollection.enabled"
|
||||
label="Enabled"
|
||||
color="primary"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
@@ -243,7 +233,7 @@ watch(() => props.modelValue, async (newValue) => {
|
||||
<v-card-actions class="justify-space-between align-center">
|
||||
<div>
|
||||
<v-btn
|
||||
v-if="mode === 'edit' && editingCollectionService?.capabilities?.CollectionDestroy"
|
||||
v-if="mode === 'edit' && editingCollectionService?.capable('CollectionDestroy')"
|
||||
color="error"
|
||||
variant="text"
|
||||
@click="onDelete"
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useCollectionsStore } from '@PeopleManager/stores/collectionsStore'
|
||||
import { CollectionObject } from '@PeopleManager/models/collection';
|
||||
|
||||
// Store
|
||||
const collectionsStore = useCollectionsStore()
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
collections: CollectionObject[]
|
||||
loading?: boolean
|
||||
selectedCollection?: CollectionObject | null
|
||||
}>()
|
||||
|
||||
@@ -17,43 +14,14 @@ const emit = defineEmits<{
|
||||
'edit': [collection: CollectionObject]
|
||||
}>()
|
||||
|
||||
// State
|
||||
const loading = ref(false)
|
||||
const collections = ref<CollectionObject[]>([])
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
collections.value = await collectionsStore.list()
|
||||
} catch (error) {
|
||||
console.error('[People] - Failed to load collections:', error)
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
// Functions
|
||||
const onCollectionSelect = (collection: CollectionObject) => {
|
||||
console.log('[People] - Collection selected', collection)
|
||||
emit('select', collection)
|
||||
}
|
||||
|
||||
const onCollectionEdit = (collection: CollectionObject) => {
|
||||
emit('edit', collection)
|
||||
}
|
||||
|
||||
// Expose refresh method
|
||||
defineExpose({
|
||||
async refresh() {
|
||||
loading.value = true
|
||||
try {
|
||||
collections.value = await collectionsStore.list()
|
||||
} catch (error) {
|
||||
console.error('[People] - Failed to load collections:', error)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -73,18 +41,18 @@ defineExpose({
|
||||
<v-list v-else density="compact" nav class="pa-0">
|
||||
<v-list-item
|
||||
v-for="collection in collections"
|
||||
:key="collection.id"
|
||||
:value="collection.id"
|
||||
:active="selectedCollection?.id === collection.id"
|
||||
:key="collection.identifier"
|
||||
:value="collection.identifier"
|
||||
:active="selectedCollection?.identifier === collection.identifier"
|
||||
@click="onCollectionSelect(collection)"
|
||||
rounded="lg"
|
||||
class="mb-1"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-icon :color="collection.color || 'primary'" icon="mdi-book-outline" size="small" />
|
||||
<v-icon :color="collection.properties.color || 'primary'" icon="mdi-book-outline" size="small" />
|
||||
</template>
|
||||
|
||||
<v-list-item-title>{{ collection.label }}</v-list-item-title>
|
||||
<v-list-item-title>{{ collection.properties.label || 'Unnamed' }}</v-list-item-title>
|
||||
|
||||
<template #append>
|
||||
<v-btn
|
||||
|
||||
@@ -37,9 +37,9 @@ const saving = ref(false)
|
||||
// Computed
|
||||
const mode = computed(() => props.mode)
|
||||
const entity = computed(() => props.selectedEntity || null)
|
||||
const entityObject = computed(() => entity.value?.data ?? null)
|
||||
const entityFresh = computed(() => entity.value?.id === null || entity.value?.id === undefined)
|
||||
const entityType = computed(() => entityObject.value?.type || 'individual')
|
||||
const entityObject = computed(() => entity.value?.properties ?? null)
|
||||
const entityFresh = computed(() => !entity.value?.identifier)
|
||||
const entityType = computed(() => (entityObject.value as any)?.type || 'individual')
|
||||
|
||||
// Determine which sections to show based on entity type
|
||||
const showNames = computed(() => entityType.value === 'individual')
|
||||
@@ -194,7 +194,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
<div class="people-editor-header d-flex justify-space-between pa-4">
|
||||
<div>
|
||||
<v-icon :icon="entityIcon" class="mr-2" />
|
||||
{{ entity?.data?.label || 'Nothing Selected' }}
|
||||
{{ entity?.properties?.label || 'Nothing Selected' }}
|
||||
</div>
|
||||
|
||||
<div v-if="!loading && entity">
|
||||
@@ -236,20 +236,20 @@ const removeVirtualLocation = (key: string) => {
|
||||
<PersonEditorLabel
|
||||
v-if="showLabel"
|
||||
:mode="mode"
|
||||
:label="entity.data!.label"
|
||||
@update:label="entity.data!.label = $event"
|
||||
:label="(entityObject as any)?.label"
|
||||
@update:label="(entityObject as any).label = $event"
|
||||
/>
|
||||
|
||||
<PersonEditorNames
|
||||
v-if="showNames"
|
||||
:mode="mode"
|
||||
:names="entity.data!.names"
|
||||
@update:names="entity.data!.names = $event"
|
||||
:names="(entityObject as any)?.names"
|
||||
@update:names="(entityObject as any).names = $event"
|
||||
/>
|
||||
|
||||
<PersonEditorTitles
|
||||
v-if="showTitles"
|
||||
:titles="(entity.data as any)!.titles || {}"
|
||||
:titles="(entityObject as any)?.titles || {}"
|
||||
:mode="mode"
|
||||
@add-title="addTitle"
|
||||
@remove-title="removeTitle"
|
||||
@@ -257,7 +257,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorAnniversaries
|
||||
v-if="showAnniversaries"
|
||||
:anniversaries="(entity.data as any)!.anniversaries || []"
|
||||
:anniversaries="(entityObject as any)?.anniversaries || []"
|
||||
:mode="mode"
|
||||
@add-anniversary="addAnniversary"
|
||||
@remove-anniversary="removeAnniversary"
|
||||
@@ -266,7 +266,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorPhysicalLocations
|
||||
v-if="showPhysicalLocations"
|
||||
:physical-locations="(entity.data as any)!.physicalLocations || {}"
|
||||
:physical-locations="(entityObject as any)?.physicalLocations || {}"
|
||||
:mode="mode"
|
||||
@add-physical-location="addPhysicalLocation"
|
||||
@remove-physical-location="removePhysicalLocation"
|
||||
@@ -274,7 +274,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorPhones
|
||||
v-if="showPhones"
|
||||
:phones="(entity.data as any)!.phones || {}"
|
||||
:phones="(entityObject as any)?.phones || {}"
|
||||
:mode="mode"
|
||||
@add-phone="addPhone"
|
||||
@remove-phone="removePhone"
|
||||
@@ -282,7 +282,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorEmails
|
||||
v-if="showEmails"
|
||||
:emails="(entity.data as any)!.emails || {}"
|
||||
:emails="(entityObject as any)?.emails || {}"
|
||||
:mode="mode"
|
||||
@add-email="addEmail"
|
||||
@remove-email="removeEmail"
|
||||
@@ -290,7 +290,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorVirtualLocations
|
||||
v-if="showVirtualLocations"
|
||||
:virtual-locations="(entity.data as any)!.virtualLocations || {}"
|
||||
:virtual-locations="(entityObject as any)?.virtualLocations || {}"
|
||||
:mode="mode"
|
||||
@add-virtual-location="addVirtualLocation"
|
||||
@remove-virtual-location="removeVirtualLocation"
|
||||
@@ -298,7 +298,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorOrganizations
|
||||
v-if="showOrganizations"
|
||||
:organizations="(entity.data as any)!.organizations || {}"
|
||||
:organizations="(entityObject as any)?.organizations || {}"
|
||||
:mode="mode"
|
||||
@add-organization="addOrganization"
|
||||
@remove-organization="removeOrganization"
|
||||
@@ -306,7 +306,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorMembers
|
||||
v-if="showMembers"
|
||||
:members="(entity.data as any)!.members || {}"
|
||||
:members="(entityObject as any)?.members || {}"
|
||||
:mode="mode"
|
||||
@add-member="addMember"
|
||||
@remove-member="removeMember"
|
||||
@@ -314,7 +314,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorNotes
|
||||
v-if="showNotes"
|
||||
:notes="entity.data!.notes || {}"
|
||||
:notes="(entityObject as any)?.notes || {}"
|
||||
:mode="mode"
|
||||
@add-note="addNote"
|
||||
@remove-note="removeNote"
|
||||
@@ -322,7 +322,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorLanguages
|
||||
v-if="showLanguages"
|
||||
:languages="(entity.data as any)!.languages || []"
|
||||
:languages="(entityObject as any)?.languages || []"
|
||||
:mode="mode"
|
||||
@add-language="addLanguage"
|
||||
@remove-language="removeLanguage"
|
||||
@@ -330,7 +330,7 @@ const removeVirtualLocation = (key: string) => {
|
||||
|
||||
<PersonEditorMedia
|
||||
v-if="showMedia"
|
||||
:media="entity.data!.media || {}"
|
||||
:media="(entityObject as any)?.media || {}"
|
||||
:mode="mode"
|
||||
@add-media="addMedia"
|
||||
@remove-media="removeMedia"
|
||||
|
||||
@@ -31,8 +31,15 @@ const filteredEntities = computed(() => {
|
||||
}
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
return collectionEntities.value.filter(entity => {
|
||||
const name = `${entity.data?.names.family || ''} ${entity.data?.names.given || ''}`.toLowerCase()
|
||||
const email = (entity.data?.emails?.[0]?.address || '').toLowerCase()
|
||||
const props = (entity.properties as any)
|
||||
const given = props?.names?.given || ''
|
||||
const family = props?.names?.family || ''
|
||||
const full = props?.names?.full || ''
|
||||
const label = props?.label || ''
|
||||
const name = `${given} ${family} ${full} ${label}`.toLowerCase()
|
||||
const emails = props?.emails
|
||||
const firstEmail = emails ? Object.values(emails)[0] as any : null
|
||||
const email = (firstEmail?.address || '').toLowerCase()
|
||||
return name.includes(query) || email.includes(query)
|
||||
})
|
||||
})
|
||||
@@ -42,13 +49,17 @@ watch(() => props.selectedCollection, async (newCollection) => {
|
||||
if (newCollection) {
|
||||
loading.value = true
|
||||
try {
|
||||
collectionEntities.value = await entitiesStore.list(
|
||||
newCollection.provider,
|
||||
newCollection.service,
|
||||
newCollection.id
|
||||
);
|
||||
const sources = {
|
||||
[newCollection.provider]: {
|
||||
[String(newCollection.service)]: {
|
||||
[String(newCollection.identifier)]: true as const,
|
||||
},
|
||||
},
|
||||
}
|
||||
const result = await entitiesStore.list(sources)
|
||||
collectionEntities.value = Object.values(result)
|
||||
} catch (error) {
|
||||
console.error('[People] - Failed to load contacts:', error);
|
||||
console.error('[People] - Failed to load contacts:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -56,11 +67,11 @@ watch(() => props.selectedCollection, async (newCollection) => {
|
||||
collectionEntities.value = []
|
||||
loading.value = false
|
||||
}
|
||||
}, { immediate: true });
|
||||
}, { immediate: true })
|
||||
|
||||
// Functions
|
||||
function entityType(entity: EntityObject): string {
|
||||
return entity.data?.type || 'individual'
|
||||
return (entity.properties as any)?.type || 'individual'
|
||||
}
|
||||
|
||||
function entityIcon(entity: EntityObject): string {
|
||||
@@ -76,62 +87,65 @@ function entityIcon(entity: EntityObject): string {
|
||||
}
|
||||
|
||||
function entityPhoto(entity: EntityObject): string | null {
|
||||
// TODO: Implement photo retrieval from entity data
|
||||
// TODO: Implement photo retrieval from entity properties
|
||||
return null
|
||||
}
|
||||
|
||||
function entityInitials(entity: EntityObject): string {
|
||||
const type = entityType(entity)
|
||||
const props = (entity.properties as any)
|
||||
|
||||
if (type === 'organization') {
|
||||
const name = (entity.data as any)?.names?.full || entity.data?.label || ''
|
||||
const name = props?.names?.full || props?.label || ''
|
||||
return name.substring(0, 2).toUpperCase() || 'OR'
|
||||
}
|
||||
|
||||
if (type === 'group') {
|
||||
const name = (entity.data as any)?.names?.full || entity.data?.label || ''
|
||||
const name = props?.names?.full || props?.label || ''
|
||||
return name.substring(0, 2).toUpperCase() || 'GR'
|
||||
}
|
||||
|
||||
// Individual
|
||||
const given = (entity.data as any)?.names?.given
|
||||
const family = (entity.data as any)?.names?.family
|
||||
const given = props?.names?.given
|
||||
const family = props?.names?.family
|
||||
const initials = `${given?.[0] || ''}${family?.[0] || ''}`.toUpperCase()
|
||||
return initials || '?'
|
||||
}
|
||||
|
||||
function entityLabel(entity: EntityObject): string {
|
||||
const type = entityType(entity)
|
||||
const props = (entity.properties as any)
|
||||
|
||||
if (type === 'organization' || type === 'group') {
|
||||
return entity.data?.label || (entity.data as any)?.names?.full || 'Unknown'
|
||||
return props?.label || props?.names?.full || 'Unknown'
|
||||
}
|
||||
|
||||
// Individual
|
||||
return entity.data?.label || `${(entity.data as any)?.names?.given || ''} ${(entity.data as any)?.names?.family || ''}`.trim() || 'Unknown'
|
||||
const given = props?.names?.given || ''
|
||||
const family = props?.names?.family || ''
|
||||
return props?.label || `${given} ${family}`.trim() || 'Unknown'
|
||||
}
|
||||
|
||||
function entityEmail(entity: EntityObject): string | null {
|
||||
const emails = (entity.data as any)?.emails
|
||||
const emails = (entity.properties as any)?.emails
|
||||
if (!emails) return null
|
||||
const emailEntries = Object.values(emails)
|
||||
const emailEntries = Object.values(emails) as any[]
|
||||
if (emailEntries.length === 0) return null
|
||||
// Sort by priority (assuming lower number = higher priority)
|
||||
emailEntries.sort((a: any, b: any) => (a.priority || 999) - (b.priority || 999))
|
||||
return (emailEntries[0] as any).address || null
|
||||
emailEntries.sort((a, b) => (a.priority || 999) - (b.priority || 999))
|
||||
return emailEntries[0].address || null
|
||||
}
|
||||
|
||||
function entityOrganization(entity: EntityObject): string | null {
|
||||
const organizations = (entity.data as any)?.organizations
|
||||
const organizations = (entity.properties as any)?.organizations
|
||||
if (!organizations || Object.keys(organizations).length === 0) return null
|
||||
const orgEntries = Object.values(organizations)
|
||||
return (orgEntries[0] as any).Label || null
|
||||
const orgEntries = Object.values(organizations) as any[]
|
||||
return orgEntries[0].label || null
|
||||
}
|
||||
|
||||
function entityMemberCount(entity: EntityObject): number | null {
|
||||
const type = entityType(entity)
|
||||
if (type !== 'group') return null
|
||||
const members = (entity.data as any)?.members
|
||||
const members = (entity.properties as any)?.members
|
||||
if (!members) return 0
|
||||
return Object.keys(members).length
|
||||
}
|
||||
@@ -179,8 +193,8 @@ function entityAvatarColor(entity: EntityObject): string {
|
||||
<v-list v-else density="compact" nav>
|
||||
<v-list-item
|
||||
v-for="entity in filteredEntities"
|
||||
:key="entity.id"
|
||||
:value="entity.id"
|
||||
:key="entity.identifier"
|
||||
:value="entity.identifier"
|
||||
@click="$emit('select', entity)">
|
||||
|
||||
<template #prepend>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useModuleStore } from '@KTXC/stores/moduleStore'
|
||||
import { useCollectionsStore } from '@PeopleManager/stores/collectionsStore'
|
||||
import { useEntitiesStore } from '@PeopleManager/stores/entitiesStore'
|
||||
import { ServiceObject } from '@PeopleManager/models/service';
|
||||
import { CollectionObject } from '@PeopleManager/models/collection';
|
||||
import { EntityObject } from '@PeopleManager/models/entity';
|
||||
import { usePeopleStore } from '@/stores/peopleStore'
|
||||
import CollectionList from '@/components/CollectionList.vue'
|
||||
import CollectionEditor from '@/components/CollectionEditor.vue'
|
||||
import PersonList from '@/components/PersonList.vue'
|
||||
@@ -15,148 +12,50 @@ import PersonEditor from '@/components/PersonEditor.vue'
|
||||
// Vuetify display
|
||||
const display = useDisplay()
|
||||
|
||||
// View state
|
||||
const sidebarVisible = ref(true)
|
||||
|
||||
// Check if people manager is available
|
||||
const moduleStore = useModuleStore()
|
||||
|
||||
const isPeopleManagerAvailable = computed(() => {
|
||||
return moduleStore.has('people_manager') || moduleStore.has('PeopleManager')
|
||||
})
|
||||
|
||||
// Local state for UI selections
|
||||
const collectionsStore = useCollectionsStore()
|
||||
const entitiesStore = useEntitiesStore()
|
||||
const selectedCollection = ref<CollectionObject | null>(null)
|
||||
const selectedEntity = ref<EntityObject | null>(null)
|
||||
const entityEditorMode = ref<'edit' | 'view'>('view')
|
||||
// Store
|
||||
const peopleStore = usePeopleStore()
|
||||
|
||||
// Collection editor state
|
||||
const showCollectionEditor = ref(false)
|
||||
const editingCollection = ref<CollectionObject | null>(null)
|
||||
const collectionEditorMode = ref<'create' | 'edit'>('create')
|
||||
const CollectionListRef = ref<InstanceType<typeof CollectionList> | null>(null)
|
||||
const {
|
||||
sidebarVisible,
|
||||
loading,
|
||||
selectedCollection,
|
||||
selectedEntity,
|
||||
entityEditorMode,
|
||||
showCollectionEditor,
|
||||
editingCollection,
|
||||
collectionEditorMode,
|
||||
collections,
|
||||
} = storeToRefs(peopleStore)
|
||||
|
||||
// Handlers
|
||||
const handleEditorEdit = () => {
|
||||
console.log('[People] - Editor editing started')
|
||||
entityEditorMode.value = 'edit'
|
||||
}
|
||||
const {
|
||||
initialize,
|
||||
selectCollection,
|
||||
openEditCollection,
|
||||
openCreateCollection,
|
||||
selectEntity,
|
||||
createEntity,
|
||||
startEditingEntity,
|
||||
cancelEditingEntity,
|
||||
closeEntityEditor,
|
||||
saveEntity,
|
||||
deleteEntity,
|
||||
saveCollection,
|
||||
deleteCollection,
|
||||
} = peopleStore
|
||||
|
||||
const handleEditorCancel = () => {
|
||||
console.log('[People] - Editor editing cancelled')
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
const handleEditorClose = () => {
|
||||
console.log('[People] - Editor closed')
|
||||
selectedEntity.value = null
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
const handleCollectionSelect = (collection: CollectionObject) => {
|
||||
console.log('[People] - Collection selected', collection)
|
||||
selectedCollection.value = collection ?? null
|
||||
selectedEntity.value = null
|
||||
}
|
||||
|
||||
const handleCollectionEdit = (collection: CollectionObject) => {
|
||||
console.log('[People] - Collection edit', collection)
|
||||
editingCollection.value = collection
|
||||
collectionEditorMode.value = 'edit'
|
||||
showCollectionEditor.value = true
|
||||
}
|
||||
|
||||
const onCollectionFresh = () => {
|
||||
console.log('[People] - Collection new')
|
||||
editingCollection.value = collectionsStore.fresh()
|
||||
collectionEditorMode.value = 'create'
|
||||
showCollectionEditor.value = true
|
||||
}
|
||||
|
||||
const handleCollectionSave = async (collection: CollectionObject, service: ServiceObject) => {
|
||||
console.log('[People] - Collection save', collection)
|
||||
if (collectionEditorMode.value === 'create') {
|
||||
await handleCollectionCreate(collection, service)
|
||||
} else {
|
||||
await handleCollectionModify(collection)
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await initialize()
|
||||
} catch (error) {
|
||||
console.error('[People] - Failed to load data from PeopleManager:', error)
|
||||
}
|
||||
await CollectionListRef.value?.refresh()
|
||||
}
|
||||
|
||||
const handleCollectionCreate = async (collection: CollectionObject, service: ServiceObject) => {
|
||||
console.log('[People] - Collection created', collection)
|
||||
const result = await collectionsStore.create(service, collection)
|
||||
|
||||
if (result instanceof CollectionObject) {
|
||||
selectedCollection.value = result
|
||||
} else {
|
||||
selectedCollection.value = null
|
||||
}
|
||||
|
||||
selectedEntity.value = null
|
||||
}
|
||||
|
||||
const handleCollectionModify = async (collection: CollectionObject) => {
|
||||
console.log('[People] - Collection modified', collection)
|
||||
await collectionsStore.modify(collection)
|
||||
}
|
||||
|
||||
const handleCollectionDelete = async (collection: CollectionObject) => {
|
||||
console.log('[People] - Collection deleted', collection)
|
||||
await collectionsStore.destroy(collection)
|
||||
selectedCollection.value = null
|
||||
selectedEntity.value = null
|
||||
await CollectionListRef.value?.refresh()
|
||||
}
|
||||
|
||||
const handleEntitySelect = (entity: EntityObject) => {
|
||||
console.log('[People] - Entity selected', entity)
|
||||
selectedEntity.value = entity
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
const onEntityFresh = (type: string) => {
|
||||
console.log('[People] - Entity create', type)
|
||||
selectedEntity.value = entitiesStore.fresh(type)
|
||||
entityEditorMode.value = 'edit'
|
||||
}
|
||||
|
||||
const handleEntitySave = async (entity: EntityObject, collection?: CollectionObject | null) => {
|
||||
console.log('[People] - Entity save', entity)
|
||||
if (!(collection instanceof CollectionObject)) {
|
||||
collection = selectedCollection.value
|
||||
}
|
||||
if (!collection) {
|
||||
console.warn('[People] - Invalid collection object cannot save entity')
|
||||
return
|
||||
}
|
||||
if (entity.id === null) {
|
||||
console.log('[People] - Entity create', entity)
|
||||
entity.data.created = new Date();
|
||||
selectedEntity.value = await entitiesStore.create(collection, entity)
|
||||
} else if (entity.id !== null) {
|
||||
console.log('[People] - Entity modify', entity)
|
||||
entity.data.modified = new Date();
|
||||
selectedEntity.value = await entitiesStore.modify(collection, entity)
|
||||
}
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
const handleEntityDelete = async (entity: EntityObject, collection?: CollectionObject | null) => {
|
||||
console.log('[People] - Entity deleted', entity)
|
||||
if (!(collection instanceof CollectionObject)) {
|
||||
collection = selectedCollection.value
|
||||
}
|
||||
if (!collection) {
|
||||
console.warn('[People] - Invalid collection object cannot delete entity')
|
||||
return
|
||||
}
|
||||
await entitiesStore.destroy(collection, entity)
|
||||
selectedEntity.value = null
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -191,10 +90,11 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
>
|
||||
<div class="pa-4 d-flex flex-column h-100">
|
||||
<CollectionList
|
||||
ref="CollectionListRef"
|
||||
:collections="collections"
|
||||
:loading="loading"
|
||||
:selected-collection="selectedCollection"
|
||||
@select="handleCollectionSelect"
|
||||
@edit="handleCollectionEdit"
|
||||
@select="selectCollection"
|
||||
@edit="openEditCollection"
|
||||
/>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
@@ -211,7 +111,7 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
block
|
||||
class="justify-start"
|
||||
:disabled="!selectedCollection"
|
||||
@click="onEntityFresh('individual')"
|
||||
@click="createEntity('individual')"
|
||||
>
|
||||
Individual
|
||||
</v-btn>
|
||||
@@ -222,7 +122,7 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
block
|
||||
class="justify-start"
|
||||
:disabled="!selectedCollection"
|
||||
@click="onEntityFresh('organization')"
|
||||
@click="createEntity('organization')"
|
||||
>
|
||||
Organization
|
||||
</v-btn>
|
||||
@@ -233,7 +133,7 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
block
|
||||
class="justify-start"
|
||||
:disabled="!selectedCollection"
|
||||
@click="onEntityFresh('group')"
|
||||
@click="createEntity('group')"
|
||||
>
|
||||
Group
|
||||
</v-btn>
|
||||
@@ -243,7 +143,7 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
size="small"
|
||||
block
|
||||
class="justify-start"
|
||||
@click="onCollectionFresh"
|
||||
@click="openCreateCollection"
|
||||
>
|
||||
Address Book
|
||||
</v-btn>
|
||||
@@ -281,8 +181,8 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
<PersonList
|
||||
:selected-collection="selectedCollection"
|
||||
:selected-entity="selectedEntity"
|
||||
@select="handleEntitySelect"
|
||||
@fresh="onEntityFresh"
|
||||
@select="selectEntity"
|
||||
@fresh="createEntity"
|
||||
/>
|
||||
</div>
|
||||
<div class="people-editor-panel">
|
||||
@@ -290,11 +190,11 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
:mode="entityEditorMode"
|
||||
:selected-collection="selectedCollection"
|
||||
:selected-entity="selectedEntity"
|
||||
@save="handleEntitySave"
|
||||
@delete="handleEntityDelete"
|
||||
@edit="handleEditorEdit"
|
||||
@cancel="handleEditorCancel"
|
||||
@close="handleEditorClose"
|
||||
@save="saveEntity"
|
||||
@delete="deleteEntity"
|
||||
@edit="startEditingEntity"
|
||||
@cancel="cancelEditingEntity"
|
||||
@close="closeEntityEditor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -306,8 +206,8 @@ const handleEntityDelete = async (entity: EntityObject, collection?: CollectionO
|
||||
v-model="showCollectionEditor"
|
||||
:collection="editingCollection"
|
||||
:mode="collectionEditorMode"
|
||||
@save="handleCollectionSave"
|
||||
@delete="handleCollectionDelete"
|
||||
@save="saveCollection"
|
||||
@delete="deleteCollection"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
227
src/stores/peopleStore.ts
Normal file
227
src/stores/peopleStore.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { CollectionObject } from '@PeopleManager/models/collection'
|
||||
import { EntityObject } from '@PeopleManager/models/entity'
|
||||
import { IndividualObject } from '@PeopleManager/models/individual'
|
||||
import { OrganizationObject } from '@PeopleManager/models/organization'
|
||||
import { GroupObject } from '@PeopleManager/models/group'
|
||||
import type { ServiceObject } from '@PeopleManager/models/service'
|
||||
import { useCollectionsStore } from '@PeopleManager/stores/collectionsStore'
|
||||
import { useEntitiesStore } from '@PeopleManager/stores/entitiesStore'
|
||||
|
||||
export const usePeopleStore = defineStore('peopleStore', () => {
|
||||
const collectionsStore = useCollectionsStore()
|
||||
const entitiesStore = useEntitiesStore()
|
||||
|
||||
// UI state
|
||||
const sidebarVisible = ref(true)
|
||||
const loading = ref(false)
|
||||
|
||||
// Selection state
|
||||
const selectedCollection = shallowRef<CollectionObject | null>(null)
|
||||
const selectedEntity = shallowRef<EntityObject | null>(null)
|
||||
|
||||
// Entity editor state
|
||||
const entityEditorMode = ref<'edit' | 'view'>('view')
|
||||
|
||||
// Collection editor state
|
||||
const showCollectionEditor = ref(false)
|
||||
const editingCollection = shallowRef<CollectionObject | null>(null)
|
||||
const collectionEditorMode = ref<'create' | 'edit'>('create')
|
||||
|
||||
// Derived state
|
||||
const collections = computed(() => collectionsStore.collections)
|
||||
const entities = computed(() => entitiesStore.entities)
|
||||
|
||||
// --- Collection actions ---
|
||||
|
||||
function selectCollection(collection: CollectionObject) {
|
||||
selectedCollection.value = collection
|
||||
selectedEntity.value = null
|
||||
}
|
||||
|
||||
function openEditCollection(collection: CollectionObject) {
|
||||
editingCollection.value = collection
|
||||
collectionEditorMode.value = 'edit'
|
||||
showCollectionEditor.value = true
|
||||
}
|
||||
|
||||
function openCreateCollection() {
|
||||
editingCollection.value = new CollectionObject()
|
||||
collectionEditorMode.value = 'create'
|
||||
showCollectionEditor.value = true
|
||||
}
|
||||
|
||||
async function saveCollection(collection: CollectionObject, service: ServiceObject) {
|
||||
if (collectionEditorMode.value === 'create') {
|
||||
const created = await collectionsStore.create(
|
||||
service.provider,
|
||||
service.identifier || '',
|
||||
null,
|
||||
collection.properties,
|
||||
)
|
||||
selectedCollection.value = created
|
||||
} else {
|
||||
await collectionsStore.update(
|
||||
collection.provider,
|
||||
collection.service,
|
||||
collection.identifier,
|
||||
collection.properties,
|
||||
)
|
||||
}
|
||||
|
||||
showCollectionEditor.value = false
|
||||
}
|
||||
|
||||
async function deleteCollection(collection: CollectionObject) {
|
||||
await collectionsStore.delete(collection.provider, collection.service, collection.identifier)
|
||||
|
||||
if (selectedCollection.value?.identifier === collection.identifier) {
|
||||
selectedCollection.value = null
|
||||
selectedEntity.value = null
|
||||
}
|
||||
|
||||
showCollectionEditor.value = false
|
||||
}
|
||||
|
||||
// --- Entity actions ---
|
||||
|
||||
function createEntity(type: string) {
|
||||
const entity = new EntityObject()
|
||||
|
||||
if (type === 'organization') {
|
||||
entity.properties = new OrganizationObject()
|
||||
} else if (type === 'group') {
|
||||
entity.properties = new GroupObject()
|
||||
} else {
|
||||
entity.properties = new IndividualObject()
|
||||
}
|
||||
|
||||
selectedEntity.value = entity
|
||||
entityEditorMode.value = 'edit'
|
||||
}
|
||||
|
||||
function selectEntity(entity: EntityObject) {
|
||||
selectedEntity.value = entity
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
function startEditingEntity() {
|
||||
entityEditorMode.value = 'edit'
|
||||
}
|
||||
|
||||
function cancelEditingEntity() {
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
function closeEntityEditor() {
|
||||
selectedEntity.value = null
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
async function saveEntity(entity: EntityObject, collection?: CollectionObject | null) {
|
||||
const targetCollection = collection instanceof CollectionObject
|
||||
? collection
|
||||
: selectedCollection.value
|
||||
|
||||
if (!targetCollection) {
|
||||
throw new Error('[People] - No collection selected, cannot save entity')
|
||||
}
|
||||
|
||||
if (!entity.identifier) {
|
||||
// Create
|
||||
entity.properties.created = new Date()
|
||||
selectedEntity.value = await entitiesStore.create(
|
||||
targetCollection.provider,
|
||||
targetCollection.service,
|
||||
targetCollection.identifier,
|
||||
entity.properties,
|
||||
)
|
||||
} else {
|
||||
// Update
|
||||
entity.properties.modified = new Date()
|
||||
selectedEntity.value = await entitiesStore.update(
|
||||
targetCollection.provider,
|
||||
targetCollection.service,
|
||||
targetCollection.identifier,
|
||||
entity.identifier,
|
||||
entity.properties,
|
||||
)
|
||||
}
|
||||
|
||||
selectedCollection.value = targetCollection
|
||||
entityEditorMode.value = 'view'
|
||||
}
|
||||
|
||||
async function deleteEntity(entity: EntityObject, collection?: CollectionObject | null) {
|
||||
const targetCollection = collection instanceof CollectionObject
|
||||
? collection
|
||||
: selectedCollection.value
|
||||
|
||||
if (!targetCollection) {
|
||||
throw new Error('[People] - No collection selected, cannot delete entity')
|
||||
}
|
||||
|
||||
await entitiesStore.delete(
|
||||
targetCollection.provider,
|
||||
targetCollection.service,
|
||||
targetCollection.identifier,
|
||||
entity.identifier,
|
||||
)
|
||||
|
||||
closeEntityEditor()
|
||||
}
|
||||
|
||||
// --- Lifecycle ---
|
||||
|
||||
async function initialize() {
|
||||
loading.value = true
|
||||
try {
|
||||
await collectionsStore.list()
|
||||
|
||||
if (
|
||||
selectedCollection.value
|
||||
&& !collections.value.find(c => c.identifier === selectedCollection.value?.identifier)
|
||||
) {
|
||||
selectedCollection.value = null
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
sidebarVisible,
|
||||
loading,
|
||||
selectedCollection,
|
||||
selectedEntity,
|
||||
entityEditorMode,
|
||||
showCollectionEditor,
|
||||
editingCollection,
|
||||
collectionEditorMode,
|
||||
|
||||
// Derived
|
||||
collections,
|
||||
entities,
|
||||
|
||||
// Collection actions
|
||||
selectCollection,
|
||||
openEditCollection,
|
||||
openCreateCollection,
|
||||
saveCollection,
|
||||
deleteCollection,
|
||||
|
||||
// Entity actions
|
||||
createEntity,
|
||||
selectEntity,
|
||||
startEditingEntity,
|
||||
cancelEditingEntity,
|
||||
closeEntityEditor,
|
||||
saveEntity,
|
||||
deleteEntity,
|
||||
|
||||
// Lifecycle
|
||||
initialize,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user