feat: companies CRUD with person dialog integration and navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
301
apps/app/src/pages/organisation/companies.vue
Normal file
301
apps/app/src/pages/organisation/companies.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<script setup lang="ts">
|
||||
import { useCompanies, useDeleteCompany } from '@/composables/api/useCompanies'
|
||||
import { useAuthStore } from '@/stores/useAuthStore'
|
||||
import { useOrganisationStore } from '@/stores/useOrganisationStore'
|
||||
import CompanyDialog from '@/components/organisation/CompanyDialog.vue'
|
||||
import type { Company } from '@/types/organisation'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const orgStore = useOrganisationStore()
|
||||
|
||||
const orgId = computed(() => orgStore.activeOrganisationId ?? '')
|
||||
|
||||
const { data: companies, isLoading, isError, refetch } = useCompanies(orgId)
|
||||
const { mutate: deleteCompany, isPending: isDeleting } = useDeleteCompany(orgId)
|
||||
|
||||
// Search & filter
|
||||
const search = ref('')
|
||||
const filterType = ref('')
|
||||
|
||||
const typeOptions = [
|
||||
{ title: 'Alle types', value: '' },
|
||||
{ title: 'Leverancier', value: 'supplier' },
|
||||
{ title: 'Partner', value: 'partner' },
|
||||
{ title: 'Bureau', value: 'agency' },
|
||||
{ title: 'Locatie', value: 'venue' },
|
||||
{ title: 'Overig', value: 'other' },
|
||||
]
|
||||
|
||||
const typeLabel: Record<string, string> = {
|
||||
supplier: 'Leverancier',
|
||||
partner: 'Partner',
|
||||
agency: 'Bureau',
|
||||
venue: 'Locatie',
|
||||
other: 'Overig',
|
||||
}
|
||||
|
||||
const typeColor: Record<string, string> = {
|
||||
supplier: 'info',
|
||||
partner: 'success',
|
||||
agency: 'purple',
|
||||
venue: 'warning',
|
||||
other: 'default',
|
||||
}
|
||||
|
||||
const filteredCompanies = computed(() => {
|
||||
let result = companies.value ?? []
|
||||
|
||||
if (filterType.value) {
|
||||
result = result.filter(c => c.type === filterType.value)
|
||||
}
|
||||
|
||||
if (search.value) {
|
||||
const q = search.value.toLowerCase()
|
||||
result = result.filter(c => c.name.toLowerCase().includes(q))
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const headers = [
|
||||
{ title: 'Naam', key: 'name' },
|
||||
{ title: 'Type', key: 'type' },
|
||||
{ title: 'Contactpersoon', key: 'contact_name' },
|
||||
{ title: 'E-mail', key: 'contact_email' },
|
||||
{ title: 'Telefoon', key: 'contact_phone' },
|
||||
{ title: 'Acties', key: 'actions', sortable: false, align: 'end' as const },
|
||||
]
|
||||
|
||||
// Dialogs
|
||||
const isDialogOpen = ref(false)
|
||||
const editingCompany = ref<Company | null>(null)
|
||||
|
||||
const isDeleteDialogOpen = ref(false)
|
||||
const deletingCompany = ref<Company | null>(null)
|
||||
|
||||
const showSuccess = ref(false)
|
||||
const successMessage = ref('')
|
||||
|
||||
function onAdd() {
|
||||
editingCompany.value = null
|
||||
isDialogOpen.value = true
|
||||
}
|
||||
|
||||
function onEdit(company: Company) {
|
||||
editingCompany.value = company
|
||||
isDialogOpen.value = true
|
||||
}
|
||||
|
||||
function onDeleteConfirm(company: Company) {
|
||||
deletingCompany.value = company
|
||||
isDeleteDialogOpen.value = true
|
||||
}
|
||||
|
||||
function onDeleteExecute() {
|
||||
if (!deletingCompany.value) return
|
||||
const name = deletingCompany.value.name
|
||||
|
||||
deleteCompany(deletingCompany.value.id, {
|
||||
onSuccess: () => {
|
||||
isDeleteDialogOpen.value = false
|
||||
deletingCompany.value = null
|
||||
successMessage.value = `${name} verwijderd`
|
||||
showSuccess.value = true
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function onSaved() {
|
||||
successMessage.value = editingCompany.value ? 'Bedrijf bijgewerkt' : 'Bedrijf toegevoegd'
|
||||
showSuccess.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Loading -->
|
||||
<VSkeletonLoader
|
||||
v-if="isLoading"
|
||||
type="card"
|
||||
/>
|
||||
|
||||
<!-- Error -->
|
||||
<VAlert
|
||||
v-else-if="isError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
>
|
||||
Kon bedrijven niet laden.
|
||||
<template #append>
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="refetch()"
|
||||
>
|
||||
Opnieuw proberen
|
||||
</VBtn>
|
||||
</template>
|
||||
</VAlert>
|
||||
|
||||
<template v-else>
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-space-between align-center mb-6">
|
||||
<div>
|
||||
<h4 class="text-h4">
|
||||
Bedrijven
|
||||
</h4>
|
||||
<p class="text-body-1 text-disabled mb-0">
|
||||
Leveranciers, partners en andere organisaties
|
||||
</p>
|
||||
</div>
|
||||
<VBtn
|
||||
prepend-icon="tabler-plus"
|
||||
@click="onAdd"
|
||||
>
|
||||
Bedrijf toevoegen
|
||||
</VBtn>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="d-flex gap-x-4 mb-4">
|
||||
<AppTextField
|
||||
v-model="search"
|
||||
prepend-inner-icon="tabler-search"
|
||||
placeholder="Zoek op naam..."
|
||||
clearable
|
||||
style="max-inline-size: 300px;"
|
||||
/>
|
||||
<AppSelect
|
||||
v-model="filterType"
|
||||
label="Type"
|
||||
:items="typeOptions"
|
||||
clearable
|
||||
style="min-inline-size: 180px;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
<VCard
|
||||
v-if="!companies?.length"
|
||||
class="text-center pa-8"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-building"
|
||||
size="48"
|
||||
class="mb-4 text-disabled"
|
||||
/>
|
||||
<p class="text-body-1 text-disabled">
|
||||
Nog geen bedrijven. Voeg je eerste bedrijf toe.
|
||||
</p>
|
||||
</VCard>
|
||||
|
||||
<!-- No results after filter -->
|
||||
<VCard
|
||||
v-else-if="!filteredCompanies.length"
|
||||
class="text-center pa-8"
|
||||
>
|
||||
<p class="text-body-1 text-disabled">
|
||||
Geen bedrijven gevonden voor deze zoekopdracht.
|
||||
</p>
|
||||
</VCard>
|
||||
|
||||
<!-- Data table -->
|
||||
<VCard v-else>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="filteredCompanies"
|
||||
item-value="id"
|
||||
:items-per-page="-1"
|
||||
hide-default-footer
|
||||
hover
|
||||
@click:row="(_e: Event, row: { item: Company }) => onEdit(row.item)"
|
||||
>
|
||||
<template #item.type="{ item }">
|
||||
<VChip
|
||||
:color="typeColor[item.type] ?? 'default'"
|
||||
size="small"
|
||||
>
|
||||
{{ typeLabel[item.type] ?? item.type }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<template #item.contact_name="{ item }">
|
||||
{{ item.contact_name ?? '-' }}
|
||||
</template>
|
||||
|
||||
<template #item.contact_email="{ item }">
|
||||
{{ item.contact_email ?? '-' }}
|
||||
</template>
|
||||
|
||||
<template #item.contact_phone="{ item }">
|
||||
{{ item.contact_phone ?? '-' }}
|
||||
</template>
|
||||
|
||||
<template #item.actions="{ item }">
|
||||
<div class="d-flex justify-end gap-x-1">
|
||||
<VBtn
|
||||
icon="tabler-edit"
|
||||
variant="text"
|
||||
size="small"
|
||||
title="Bewerken"
|
||||
@click.stop="onEdit(item)"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
title="Verwijderen"
|
||||
@click.stop="onDeleteConfirm(item)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<!-- Create/Edit dialog -->
|
||||
<CompanyDialog
|
||||
v-model="isDialogOpen"
|
||||
:org-id="orgId"
|
||||
:company="editingCompany"
|
||||
@saved="onSaved"
|
||||
/>
|
||||
|
||||
<!-- Delete confirmation -->
|
||||
<VDialog
|
||||
v-model="isDeleteDialogOpen"
|
||||
max-width="400"
|
||||
>
|
||||
<VCard title="Bedrijf verwijderen">
|
||||
<VCardText>
|
||||
Weet je zeker dat je <strong>{{ deletingCompany?.name }}</strong> wilt verwijderen?
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="isDeleteDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="error"
|
||||
:loading="isDeleting"
|
||||
@click="onDeleteExecute"
|
||||
>
|
||||
Verwijderen
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Success snackbar -->
|
||||
<VSnackbar
|
||||
v-model="showSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
{{ successMessage }}
|
||||
</VSnackbar>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user