From 53c68f984c38d8a550ceac153ce258b0f4a7b736 Mon Sep 17 00:00:00 2001 From: Thijn Date: Tue, 17 Dec 2024 14:40:00 +0100 Subject: [PATCH 01/12] finished frontend resultaten --- src/entities/index.js | 1 + src/entities/resultaat/index.js | 3 + src/entities/resultaat/resultaat.mock.ts | 14 ++ src/entities/resultaat/resultaat.spec.ts | 12 ++ src/entities/resultaat/resultaat.ts | 32 ++++ src/entities/resultaat/resultaat.types.ts | 7 + src/modals/Modals.vue | 7 + src/modals/resultaten/DeleteResultaat.vue | 109 ++++++++++++ src/modals/resultaten/ResultaatForm.vue | 194 ++++++++++++++++++++++ src/store/modules/resultaten.spec.ts | 36 ++++ src/store/modules/resultaten.ts | 174 +++++++++++++++++++ src/store/store.js | 4 + src/views/resultaten/ZaakResultaten.vue | 136 +++++++++++++++ src/views/zaken/ZaakDetails.vue | 14 +- 14 files changed, 742 insertions(+), 1 deletion(-) create mode 100644 src/entities/resultaat/index.js create mode 100644 src/entities/resultaat/resultaat.mock.ts create mode 100644 src/entities/resultaat/resultaat.spec.ts create mode 100644 src/entities/resultaat/resultaat.ts create mode 100644 src/entities/resultaat/resultaat.types.ts create mode 100644 src/modals/resultaten/DeleteResultaat.vue create mode 100644 src/modals/resultaten/ResultaatForm.vue create mode 100644 src/store/modules/resultaten.spec.ts create mode 100644 src/store/modules/resultaten.ts create mode 100644 src/views/resultaten/ZaakResultaten.vue diff --git a/src/entities/index.js b/src/entities/index.js index 0b8f0bc..188ecbe 100644 --- a/src/entities/index.js +++ b/src/entities/index.js @@ -7,3 +7,4 @@ export * from './bericht/index.js' export * from './rol/index.js' export * from './medewerkers/index.js' export * from './contactmoment/index.js' +export * from './resultaat/index.js' diff --git a/src/entities/resultaat/index.js b/src/entities/resultaat/index.js new file mode 100644 index 0000000..018cfaa --- /dev/null +++ b/src/entities/resultaat/index.js @@ -0,0 +1,3 @@ +export * from './resultaat.ts' +export * from './resultaat.types.ts' +export * from './resultaat.mock.ts' diff --git a/src/entities/resultaat/resultaat.mock.ts b/src/entities/resultaat/resultaat.mock.ts new file mode 100644 index 0000000..a0e50ba --- /dev/null +++ b/src/entities/resultaat/resultaat.mock.ts @@ -0,0 +1,14 @@ +import { Resultaat } from './resultaat' +import { TResultaat } from './resultaat.types' + +export const mockResultaatData = (): TResultaat[] => [ + { + id: '15551d6f-44e3-43f3-a9d2-59e583c91eb0', + url: 'https://api.example.com/resultaten/15551d6f-44e3-43f3-a9d2-59e583c91eb0', + zaak: '15551d6f-44e3-43f3-a9d2-59e583c91eb0', + resultaattype: 'Audit', + toelichting: 'Deze taak omvat het uitvoeren van een gedetailleerde interne audit van de bedrijfsprocessen om te controleren of alle afdelingen voldoen aan de vastgestelde kwaliteitsnormen. De bevindingen worden gedocumenteerd en er worden aanbevelingen gedaan voor verbeteringen.', + }, +] + +export const mockResultaat = (data: TResultaat[] = mockResultaatData()): TResultaat[] => data.map(item => new Resultaat(item)) diff --git a/src/entities/resultaat/resultaat.spec.ts b/src/entities/resultaat/resultaat.spec.ts new file mode 100644 index 0000000..5d402d4 --- /dev/null +++ b/src/entities/resultaat/resultaat.spec.ts @@ -0,0 +1,12 @@ +import { Resultaat } from './resultaat' +import { mockResultaatData } from './resultaat.mock' + +describe('Resultaat Entity', () => { + it('should create a Resultaat entity with full data', () => { + const resultaat = new Resultaat(mockResultaatData()[0]) + + expect(resultaat).toBeInstanceOf(Resultaat) + expect(resultaat).toEqual(mockResultaatData()[0]) + expect(resultaat.validate().success).toBe(true) + }) +}) diff --git a/src/entities/resultaat/resultaat.ts b/src/entities/resultaat/resultaat.ts new file mode 100644 index 0000000..2a050bc --- /dev/null +++ b/src/entities/resultaat/resultaat.ts @@ -0,0 +1,32 @@ +import { SafeParseReturnType, z } from 'zod' +import { TResultaat } from './resultaat.types' + +export class Resultaat implements TResultaat { + + public id: string + public url: string + public zaak: string + public resultaattype: string + public toelichting: string + + constructor(source: TResultaat) { + this.id = source.id || '' + this.url = source.url || '' + this.zaak = source.zaak || '' + this.resultaattype = source.resultaattype || '' + this.toelichting = source.toelichting || '' + } + + public validate(): SafeParseReturnType { + const schema = z.object({ + id: z.string(), + url: z.string(), + zaak: z.string(), + resultaattype: z.string(), + toelichting: z.string(), + }) + + return schema.safeParse(this) + } + +} diff --git a/src/entities/resultaat/resultaat.types.ts b/src/entities/resultaat/resultaat.types.ts new file mode 100644 index 0000000..87ab98f --- /dev/null +++ b/src/entities/resultaat/resultaat.types.ts @@ -0,0 +1,7 @@ +export type TResultaat = { + id: string; + url: string; + zaak: string; + resultaattype: string; + toelichting: string; +} diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index 75895c0..ab355e9 100644 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -32,6 +32,9 @@ import { navigationStore } from '../store/store.js' + + + @@ -55,6 +58,8 @@ import AddBerichtToZaak from './zaken/AddBerichtToZaak.vue' import AddTaakToZaak from './zaken/AddTaakToZaak.vue' import ContactMomentenForm from './contactMomenten/ContactMomentenForm.vue' import AddRolToZaak from './zaken/AddRolToZaak.vue' +import ResultaatForm from './resultaten/ResultaatForm.vue' +import DeleteResultaat from './resultaten/DeleteResultaat.vue' export default { name: 'Modals', @@ -78,6 +83,8 @@ export default { AddTaakToZaak, ContactMomentenForm, AddRolToZaak, + ResultaatForm, + DeleteResultaat, }, } diff --git a/src/modals/resultaten/DeleteResultaat.vue b/src/modals/resultaten/DeleteResultaat.vue new file mode 100644 index 0000000..3056cd5 --- /dev/null +++ b/src/modals/resultaten/DeleteResultaat.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/modals/resultaten/ResultaatForm.vue b/src/modals/resultaten/ResultaatForm.vue new file mode 100644 index 0000000..9b46315 --- /dev/null +++ b/src/modals/resultaten/ResultaatForm.vue @@ -0,0 +1,194 @@ + + + + + + + diff --git a/src/store/modules/resultaten.spec.ts b/src/store/modules/resultaten.spec.ts new file mode 100644 index 0000000..c797683 --- /dev/null +++ b/src/store/modules/resultaten.spec.ts @@ -0,0 +1,36 @@ +/* eslint-disable no-console */ +import { setActivePinia, createPinia } from 'pinia' + +import { useZaakStore } from './zaken.js' +import { Zaak, mockZaak } from '../../entities/index.js' + +describe('Zaak Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('sets zaak item correctly', () => { + const store = useZaakStore() + + store.setZaakItem(mockZaak()[0]) + + expect(store.zaakItem).toBeInstanceOf(Zaak) + expect(store.zaakItem).toEqual(mockZaak()[0]) + + expect(store.zaakItem.validate().success).toBe(true) + }) + + it('sets zaken list correctly', () => { + const store = useZaakStore() + + store.setZakenList(mockZaak()) + + expect(store.zakenList).toHaveLength(mockZaak().length) + + store.zakenList.forEach((item, index) => { + expect(item).toBeInstanceOf(Zaak) + expect(item).toEqual(mockZaak()[index]) + expect(item.validate().success).toBe(true) + }) + }) +}) diff --git a/src/store/modules/resultaten.ts b/src/store/modules/resultaten.ts new file mode 100644 index 0000000..1262119 --- /dev/null +++ b/src/store/modules/resultaten.ts @@ -0,0 +1,174 @@ +import { defineStore } from 'pinia' +import { TResultaat, Resultaat } from '../../entities/index.js' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/zrc/resultaten' + +type TOptions = { + /** + * Save the resultaat item to the store in 'resultaatItem' + */ + setItem?: boolean +} + +export const useResultaatStore = defineStore('resultaten', { + state: () => ({ + resultaatItem: null, + resultatenList: [], + /** + * Set the zaakId, used when creating a new resultaat on a zaak. + */ + zaakId: null, + }), + actions: { + setResultaatItem(resultaatItem: Resultaat | TResultaat) { + this.resultaatItem = resultaatItem && new Resultaat(resultaatItem) + console.info('Active resultaat item set to ' + resultaatItem) + }, + setResultatenList(resultatenList: Resultaat[] | TResultaat[]) { + this.resultatenList = resultatenList.map( + (resultaatItem) => new Resultaat(resultaatItem), + ) + console.info('Resultaten list set to ' + resultatenList.length + ' items') + }, + /** + * Refresh the list of resultaten items. + * + * @param search - Optional search query to filter the resultaten list. (default: `null`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TResultaat[], entities: Resultaat[] }> } The response, raw data, and entities. + */ + async refreshResultatenList(search: string = null): Promise<{ response: Response, data: TResultaat[], entities: Resultaat[] }> { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + console.info('Refreshing resultaten list with search: ' + search) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results as TResultaat[] + const entities = data.map((resultaatItem: TResultaat) => new Resultaat(resultaatItem)) + + this.setResultatenList(data) + + return { response, data, entities } + }, + /** + * Fetch a single resultaat item by its ID. + * + * @param id - The ID of the resultaat item to fetch. + * @param options - Options for fetching the resultaat item. (default: `{}`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TResultaat, entity: Resultaat }> } The response, raw data, and entity. + */ + async getResultaat( + id: string, + options: TOptions = {}, + ): Promise<{ response: Response, data: TResultaat, entity: Resultaat }> { + const endpoint = `${apiEndpoint}/${id}` + + console.info('Fetching resultaat item with id: ' + id) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Resultaat(data) + + options.setItem && this.setResultaatItem(data) + + return { response, data, entity } + }, + /** + * Delete a resultaat item from the store. + * + * @param resultaatItem - The resultaat item to delete. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteResultaat(resultaatItem: Resultaat | TResultaat): Promise<{ response: Response }> { + if (!resultaatItem) { + throw new Error('No resultaat item to delete') + } + if (!resultaatItem.id) { + throw new Error('No id for resultaat item to delete') + } + + const endpoint = `${apiEndpoint}/${resultaatItem.id}` + + console.info('Deleting resultaat item with id: ' + resultaatItem.id) + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + this.refreshResultatenList() + + return { response } + }, + /** + * Save a resultaat item to the store. If the resultaat item does not have a uuid, it will be created. + * Otherwise, it will be updated. + * + * @param resultaatItem - The resultaat item to save. + * @param options - Options for saving the resultaat item. (default: `{ setItem: true }`) + * @throws If there is no resultaat item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TResultaat, entity: Resultaat }>} The response, raw data, and entity. + */ + async saveResultaat( + resultaatItem: Resultaat | TResultaat, + options: TOptions = { setItem: true }, + ): Promise<{ response: Response, data: TResultaat, entity: Resultaat }> { + if (!resultaatItem) { + throw new Error('No resultaat item to save') + } + + const isNewResultaat = !resultaatItem?.id + const endpoint = isNewResultaat + ? `${apiEndpoint}` + : `${apiEndpoint}/${resultaatItem?.id}` + const method = isNewResultaat ? 'POST' : 'PUT' + + console.info('Saving resultaat item with id: ' + resultaatItem?.id) + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(resultaatItem), + }, + ) + + if (!response.ok) { + console.error(response) + throw new Error(response.statusText || 'Failed to save resultaat') + } + + const data = await response.json() as TResultaat + const entity = new Resultaat(data) + + options.setItem && this.setResultaatItem(data) + this.refreshResultatenList() + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/store.js b/src/store/store.js index 8734fa1..c73b63d 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -10,6 +10,7 @@ import { useTaakStore } from './modules/taak.js' import { useSearchStore } from './modules/search.ts' import { useContactMomentStore } from './modules/contactmoment.ts' import { useMedewerkerStore } from './modules/medewerkers.js' +import { useResultaatStore } from './modules/resultaten.ts' const berichtStore = useBerichtStore(pinia) const klantStore = useKlantStore(pinia) @@ -21,6 +22,8 @@ const zaakTypeStore = useZaakTypeStore(pinia) const searchStore = useSearchStore(pinia) const contactMomentStore = useContactMomentStore(pinia) const medewerkerStore = useMedewerkerStore(pinia) +const resultaatStore = useResultaatStore(pinia) + export { berichtStore, klantStore, @@ -32,4 +35,5 @@ export { searchStore, contactMomentStore, medewerkerStore, + resultaatStore, } diff --git a/src/views/resultaten/ZaakResultaten.vue b/src/views/resultaten/ZaakResultaten.vue new file mode 100644 index 0000000..ed39e4b --- /dev/null +++ b/src/views/resultaten/ZaakResultaten.vue @@ -0,0 +1,136 @@ + + + + + + diff --git a/src/views/zaken/ZaakDetails.vue b/src/views/zaken/ZaakDetails.vue index 48703ca..8826c06 100644 --- a/src/views/zaken/ZaakDetails.vue +++ b/src/views/zaken/ZaakDetails.vue @@ -1,5 +1,5 @@ Status wijzigen + + + Resultaat toevoegen + @@ -116,6 +122,9 @@ import { navigationStore, zaakStore } from '../../store/store.js' + + + @@ -188,6 +197,7 @@ import FileDocumentPlusOutline from 'vue-material-design-icons/FileDocumentPlusO import VectorPolylineEdit from 'vue-material-design-icons/VectorPolylineEdit.vue' import Eye from 'vue-material-design-icons/Eye.vue' import TimelineQuestionOutline from 'vue-material-design-icons/TimelineQuestionOutline.vue' +import FileChartCheckOutline from 'vue-material-design-icons/FileChartCheckOutline.vue' // Views import ZaakEigenschappen from '../eigenschappen/ZaakEigenschappen.vue' @@ -197,6 +207,7 @@ import ZaakTaken from '../taken/ZaakTaken.vue' import ZaakBesluiten from '../besluiten/ZaakBesluiten.vue' import ZaakDocumenten from '../documenten/ZaakDocumenten.vue' import ZakenZaken from '../zaken/ZakenZaken.vue' +import ZaakResultaten from '../resultaten/ZaakResultaten.vue' export default { name: 'ZaakDetails', @@ -214,6 +225,7 @@ export default { ZaakBesluiten, ZaakDocumenten, ZakenZaken, + ZaakResultaten, // Icons DotsHorizontal, Pencil, From 43fda4d87b967d3bcc70c7b3b24c991f6454f5a7 Mon Sep 17 00:00:00 2001 From: Thijn Date: Tue, 17 Dec 2024 19:51:01 +0100 Subject: [PATCH 02/12] WIP - working on besluit for zaken --- src/entities/besluit/besluit.mock.ts | 12 ++ src/entities/besluit/besluit.spec.ts | 12 ++ src/entities/besluit/besluit.ts | 26 +++ src/entities/besluit/besluit.types.ts | 5 + src/entities/besluit/index.js | 3 + src/entities/index.js | 1 + src/modals/Modals.vue | 7 + src/modals/besluiten/BesluitForm.vue | 214 +++++++++++++++++++++++++ src/modals/besluiten/DeleteBesluit.vue | 107 +++++++++++++ src/store/modules/besluiten.spec.ts | 22 +++ src/store/modules/besluiten.ts | 176 ++++++++++++++++++++ src/store/store.js | 3 + src/views/besluiten/ZaakBesluiten.vue | 92 ++++++----- src/views/zaken/ZaakDetails.vue | 10 +- 14 files changed, 647 insertions(+), 43 deletions(-) create mode 100644 src/entities/besluit/besluit.mock.ts create mode 100644 src/entities/besluit/besluit.spec.ts create mode 100644 src/entities/besluit/besluit.ts create mode 100644 src/entities/besluit/besluit.types.ts create mode 100644 src/entities/besluit/index.js create mode 100644 src/modals/besluiten/BesluitForm.vue create mode 100644 src/modals/besluiten/DeleteBesluit.vue create mode 100644 src/store/modules/besluiten.spec.ts create mode 100644 src/store/modules/besluiten.ts diff --git a/src/entities/besluit/besluit.mock.ts b/src/entities/besluit/besluit.mock.ts new file mode 100644 index 0000000..4e04e92 --- /dev/null +++ b/src/entities/besluit/besluit.mock.ts @@ -0,0 +1,12 @@ +import { Besluit } from './besluit' +import { TBesluit } from './besluit.types' + +export const mockBesluitData = (): TBesluit[] => [ + { + id: '15551d6f-44e3-43f3-a9d2-59e583c91eb0', + url: 'https://api.example.com/besluiten/15551d6f-44e3-43f3-a9d2-59e583c91eb0', + besluit: 'dsadsadasdasdadfas', + }, +] + +export const mockBesluit = (data: TBesluit[] = mockBesluitData()): TBesluit[] => data.map(item => new Besluit(item)) diff --git a/src/entities/besluit/besluit.spec.ts b/src/entities/besluit/besluit.spec.ts new file mode 100644 index 0000000..6d21b16 --- /dev/null +++ b/src/entities/besluit/besluit.spec.ts @@ -0,0 +1,12 @@ +import { Besluit } from './besluit' +import { mockBesluitData } from './besluit.mock' + +describe('Besluit Entity', () => { + it('should create a Besluit entity with full data', () => { + const besluit = new Besluit(mockBesluitData()[0]) + + expect(besluit).toBeInstanceOf(Besluit) + expect(besluit).toEqual(mockBesluitData()[0]) + expect(besluit.validate().success).toBe(true) + }) +}) diff --git a/src/entities/besluit/besluit.ts b/src/entities/besluit/besluit.ts new file mode 100644 index 0000000..08d5fb7 --- /dev/null +++ b/src/entities/besluit/besluit.ts @@ -0,0 +1,26 @@ +import { SafeParseReturnType, z } from 'zod' +import { TBesluit } from './besluit.types' + +export class Besluit implements TBesluit { + + public id: string + public url: string + public besluit: string + + constructor(source: TBesluit) { + this.id = source.id || '' + this.url = source.url || '' + this.besluit = source.besluit || '' + } + + public validate(): SafeParseReturnType { + const schema = z.object({ + id: z.string(), + url: z.string(), + besluit: z.string(), + }) + + return schema.safeParse(this) + } + +} diff --git a/src/entities/besluit/besluit.types.ts b/src/entities/besluit/besluit.types.ts new file mode 100644 index 0000000..2be07f8 --- /dev/null +++ b/src/entities/besluit/besluit.types.ts @@ -0,0 +1,5 @@ +export type TBesluit = { + id: string; + url: string; + besluit: string; +} diff --git a/src/entities/besluit/index.js b/src/entities/besluit/index.js new file mode 100644 index 0000000..27a05c3 --- /dev/null +++ b/src/entities/besluit/index.js @@ -0,0 +1,3 @@ +export * from './besluit.ts' +export * from './besluit.types.ts' +export * from './besluit.mock.ts' diff --git a/src/entities/index.js b/src/entities/index.js index 188ecbe..4ba60de 100644 --- a/src/entities/index.js +++ b/src/entities/index.js @@ -8,3 +8,4 @@ export * from './rol/index.js' export * from './medewerkers/index.js' export * from './contactmoment/index.js' export * from './resultaat/index.js' +export * from './besluit/index.js' diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index ab355e9..9703b6d 100644 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -35,6 +35,9 @@ import { navigationStore } from '../store/store.js' + + + @@ -60,6 +63,8 @@ import ContactMomentenForm from './contactMomenten/ContactMomentenForm.vue' import AddRolToZaak from './zaken/AddRolToZaak.vue' import ResultaatForm from './resultaten/ResultaatForm.vue' import DeleteResultaat from './resultaten/DeleteResultaat.vue' +import BesluitForm from './besluiten/BesluitForm.vue' +import DeleteBesluit from './besluiten/DeleteBesluit.vue' export default { name: 'Modals', @@ -85,6 +90,8 @@ export default { AddRolToZaak, ResultaatForm, DeleteResultaat, + BesluitForm, + DeleteBesluit, }, } diff --git a/src/modals/besluiten/BesluitForm.vue b/src/modals/besluiten/BesluitForm.vue new file mode 100644 index 0000000..07d1ca4 --- /dev/null +++ b/src/modals/besluiten/BesluitForm.vue @@ -0,0 +1,214 @@ + + + + + + + diff --git a/src/modals/besluiten/DeleteBesluit.vue b/src/modals/besluiten/DeleteBesluit.vue new file mode 100644 index 0000000..ec0aa11 --- /dev/null +++ b/src/modals/besluiten/DeleteBesluit.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/src/store/modules/besluiten.spec.ts b/src/store/modules/besluiten.spec.ts new file mode 100644 index 0000000..551872f --- /dev/null +++ b/src/store/modules/besluiten.spec.ts @@ -0,0 +1,22 @@ +/* eslint-disable no-console */ +import { setActivePinia, createPinia } from 'pinia' + +import { useBesluitStore } from './besluiten.js' +import { Besluit, mockBesluit } from '../../entities/index.js' + +describe('Besluit Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('sets besluit item correctly', () => { + const store = useBesluitStore() + + store.setBesluitItem(mockBesluit()[0]) + + expect(store.besluitItem).toBeInstanceOf(Besluit) + expect(store.besluitItem).toEqual(mockBesluit()[0]) + + expect(store.besluitItem.validate().success).toBe(true) + }) +}) diff --git a/src/store/modules/besluiten.ts b/src/store/modules/besluiten.ts new file mode 100644 index 0000000..99c7cc1 --- /dev/null +++ b/src/store/modules/besluiten.ts @@ -0,0 +1,176 @@ +import { defineStore } from 'pinia' +import { TBesluit, Besluit } from '../../entities/index.js' + +const getApiEndpoint = (zaakId: string) => `/index.php/apps/zaakafhandelapp/api/zrc/zaken/${zaakId}/besluiten` + +type TOptions = { + /** + * Save the besluit item to the store in 'besluitItem' + */ + setItem?: boolean +} + +export const useBesluitStore = defineStore('besluiten', { + state: () => ({ + besluitItem: null, + zaakId: null, + }), + actions: { + setBesluitItem(besluitItem: Besluit | TBesluit) { + this.besluitItem = besluitItem && new Besluit(besluitItem) + console.info('Active besluit item set to ' + besluitItem) + }, + /** + * Fetch a single besluit item by its ID. + * + * @param zaakId - The ID of the zaak the besluit belongs to + * @param id - The ID of the besluit item to fetch. + * @param options - Options for fetching the besluit item. (default: `{}`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TBesluit, entity: Besluit }> } The response, raw data, and entity. + */ + async getBesluit( + zaakId: string, + id: string, + options: TOptions = {}, + ): Promise<{ response: Response, data: TBesluit, entity: Besluit }> { + const endpoint = `${getApiEndpoint(zaakId)}/${id}` + + console.info('Fetching besluit item with id: ' + id + ' from zaak: ' + zaakId) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Besluit(data) + + options.setItem && this.setBesluitItem(data) + + return { response, data, entity } + }, + /** + * Fetch all besluiten for a zaak. + * + * @param zaakId - The ID of the zaak to fetch besluiten for + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TBesluit[], entities: Besluit[] }> } The response, raw data array, and entity array. + */ + async getBesluiten( + zaakId: string, + ): Promise<{ response: Response, data: TBesluit[], entities: Besluit[] }> { + const endpoint = getApiEndpoint(zaakId) + + console.info('Fetching besluiten for zaak: ' + zaakId) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const _data = (await response.json()).results as { [key: string]: TBesluit } + + /* + As of now (17/12/2024) the data gets send back in this format + { + "results": { + "5137a1e5-b54d-43ad-abd1-4b5bff5fcd3f": { + "id": "5137a1e5-b54d-43ad-abd1-4b5bff5fcd3f", + "name": "Zaakt type 1", + "summary": "summary for one" + }, + } + } + Instead of the expected array of objects. + So some processing is needed to get the data in the expected format. + */ + + const data = Object.values(_data) + const entities = data.map((item: TBesluit) => new Besluit(item)) + + return { response, data, entities } + }, + /** + * Delete a besluit item from the store. + * + * @param zaakId - The ID of the zaak the besluit belongs to + * @param besluitId - The ID of the besluit item to delete. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteBesluit(zaakId: string, besluitId: string): Promise<{ response: Response }> { + if (!besluitId) { + throw new Error('No besluit item to delete') + } + + const endpoint = `${getApiEndpoint(zaakId)}/${besluitId}` + + console.info('Deleting besluit item with id: ' + besluitId + ' from zaak: ' + zaakId) + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + return { response } + }, + /** + * Save a besluit item to the store. If the besluit item does not have a uuid, it will be created. + * Otherwise, it will be updated. + * + * @param zaakId - The ID of the zaak the besluit belongs to + * @param besluitItem - The besluit item to save. + * @param options - Options for saving the besluit item. (default: `{ setItem: true }`) + * @throws If there is no besluit item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TBesluit, entity: Besluit }>} The response, raw data, and entity. + */ + async saveBesluit( + zaakId: string, + besluitItem: Besluit | TBesluit, + options: TOptions = { setItem: true }, + ): Promise<{ response: Response, data: TBesluit, entity: Besluit }> { + if (!besluitItem) { + throw new Error('No besluit item to save') + } + + const isNewBesluit = !besluitItem?.id + const endpoint = isNewBesluit + ? getApiEndpoint(zaakId) + : `${getApiEndpoint(zaakId)}/${besluitItem?.id}` + const method = isNewBesluit ? 'POST' : 'PUT' + + console.info('Saving besluit item with id: ' + besluitItem?.id + ' to zaak: ' + zaakId) + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(besluitItem), + }, + ) + + if (!response.ok) { + console.error(response) + throw new Error(response.statusText || 'Failed to save besluit') + } + + const data = await response.json() as TBesluit + const entity = new Besluit(data) + + options.setItem && this.setBesluitItem(data) + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/store.js b/src/store/store.js index c73b63d..d2c2fce 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -11,6 +11,7 @@ import { useSearchStore } from './modules/search.ts' import { useContactMomentStore } from './modules/contactmoment.ts' import { useMedewerkerStore } from './modules/medewerkers.js' import { useResultaatStore } from './modules/resultaten.ts' +import { useBesluitStore } from './modules/besluiten.ts' const berichtStore = useBerichtStore(pinia) const klantStore = useKlantStore(pinia) @@ -23,6 +24,7 @@ const searchStore = useSearchStore(pinia) const contactMomentStore = useContactMomentStore(pinia) const medewerkerStore = useMedewerkerStore(pinia) const resultaatStore = useResultaatStore(pinia) +const besluitStore = useBesluitStore(pinia) export { berichtStore, @@ -36,4 +38,5 @@ export { contactMomentStore, medewerkerStore, resultaatStore, + besluitStore, } diff --git a/src/views/besluiten/ZaakBesluiten.vue b/src/views/besluiten/ZaakBesluiten.vue index e4db490..6ed93b5 100644 --- a/src/views/besluiten/ZaakBesluiten.vue +++ b/src/views/besluiten/ZaakBesluiten.vue @@ -1,41 +1,46 @@ + + + + + + + + diff --git a/src/modals/besluiten/DeleteBesluit.vue b/src/modals/besluiten/DeleteBesluit.vue index d543155..6b4943f 100644 --- a/src/modals/besluiten/DeleteBesluit.vue +++ b/src/modals/besluiten/DeleteBesluit.vue @@ -1,100 +1,100 @@ - - - - - + + + + + diff --git a/src/modals/contactMomenten/ContactMomentenForm.vue b/src/modals/contactMomenten/ContactMomentenForm.vue index 6edc158..eca9c7e 100644 --- a/src/modals/contactMomenten/ContactMomentenForm.vue +++ b/src/modals/contactMomenten/ContactMomentenForm.vue @@ -1,1202 +1,1202 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/modals/contactMomenten/ViewContactMoment.vue b/src/modals/contactMomenten/ViewContactMoment.vue index 36a3e8e..3df2aa4 100644 --- a/src/modals/contactMomenten/ViewContactMoment.vue +++ b/src/modals/contactMomenten/ViewContactMoment.vue @@ -1,780 +1,780 @@ - - - - - - - - - + + + + + + + + + diff --git a/src/modals/resultaten/DeleteResultaat.vue b/src/modals/resultaten/DeleteResultaat.vue index 3056cd5..ae1aa1a 100644 --- a/src/modals/resultaten/DeleteResultaat.vue +++ b/src/modals/resultaten/DeleteResultaat.vue @@ -1,109 +1,109 @@ - - - - - + + + + + diff --git a/src/modals/resultaten/ResultaatForm.vue b/src/modals/resultaten/ResultaatForm.vue index 9b46315..344f354 100644 --- a/src/modals/resultaten/ResultaatForm.vue +++ b/src/modals/resultaten/ResultaatForm.vue @@ -1,194 +1,194 @@ - - - - - - - + + + + + + + diff --git a/src/router/router.ts b/src/router/router.ts index 8863978..2c3e64b 100644 --- a/src/router/router.ts +++ b/src/router/router.ts @@ -1,72 +1,72 @@ -// =============================================== -// Please do not touch this if you don't know what you're doing -// Even I barely know what I'm doing -// -// If you want to learn more, ask GPT about things like nested routes -// =============================================== - -import type { RawLocation, Route } from 'vue-router' -import type { ErrorHandler } from 'vue-router/types/router.d.ts' - -import { generateUrl } from '@nextcloud/router' -import Router from 'vue-router' -import Vue from 'vue' - -// Pages -import Views from '../views/Views.vue' -import DashboardIndex from '../views/dashboard/DashboardIndex.vue' - -// Prevent router from throwing errors when we're already on the page we're trying to go to -const originalPush = Router.prototype.push as (to: any, onComplete?: any, onAbort?: any) => Promise -Router.prototype.push = function push(to: RawLocation, onComplete?: ((route: Route) => void) | undefined, onAbort?: ErrorHandler | undefined): Promise { - if (onComplete || onAbort) return originalPush.call(this, to, onComplete, onAbort) - return originalPush.call(this, to).catch((err: any) => err) -} - -Vue.use(Router) - -const router = new Router({ - mode: 'history', - - // if index.php is in the url AND we got this far, then it's working: - // let's keep using index.php in the url - base: generateUrl('/apps/zaakafhandelapp'), - linkActiveClass: 'active', - - routes: [ - { - path: '/', - component: Views, - children: [ - { - path: '', - name: 'dashboard', - component: DashboardIndex, - }, - // A generic route that captures the view name and optional ID - { - path: ':view/:id?', - name: 'dynamic-view', - // Instead of directly specifying a component here, - // we will let Views.vue handle dynamic component loading. - // So we can either define a placeholder component here or none. - // If we leave it empty, it means Views will have - // and we dynamically load inside Views. - // But to keep it simple, let's just keep Views.vue responsible for it. - component: { - // A simple dummy component that just displays a - // This can also be done by making Views handle directly. - // But let's assume Views.vue will have the . - render(h: any) { return h('div', [h('router-view')]) }, - }, - // used to watch for changes in the views modal - // otherwise it wont reload the component - meta: { watchParam: 'id' }, - }, - ], - - }, - ], -}) - -export default router +// =============================================== +// Please do not touch this if you don't know what you're doing +// Even I barely know what I'm doing +// +// If you want to learn more, ask GPT about things like nested routes +// =============================================== + +import type { RawLocation, Route } from 'vue-router' +import type { ErrorHandler } from 'vue-router/types/router.d.ts' + +import { generateUrl } from '@nextcloud/router' +import Router from 'vue-router' +import Vue from 'vue' + +// Pages +import Views from '../views/Views.vue' +import DashboardIndex from '../views/dashboard/DashboardIndex.vue' + +// Prevent router from throwing errors when we're already on the page we're trying to go to +const originalPush = Router.prototype.push as (to: any, onComplete?: any, onAbort?: any) => Promise +Router.prototype.push = function push(to: RawLocation, onComplete?: ((route: Route) => void) | undefined, onAbort?: ErrorHandler | undefined): Promise { + if (onComplete || onAbort) return originalPush.call(this, to, onComplete, onAbort) + return originalPush.call(this, to).catch((err: any) => err) +} + +Vue.use(Router) + +const router = new Router({ + mode: 'history', + + // if index.php is in the url AND we got this far, then it's working: + // let's keep using index.php in the url + base: generateUrl('/apps/zaakafhandelapp'), + linkActiveClass: 'active', + + routes: [ + { + path: '/', + component: Views, + children: [ + { + path: '', + name: 'dashboard', + component: DashboardIndex, + }, + // A generic route that captures the view name and optional ID + { + path: ':view/:id?', + name: 'dynamic-view', + // Instead of directly specifying a component here, + // we will let Views.vue handle dynamic component loading. + // So we can either define a placeholder component here or none. + // If we leave it empty, it means Views will have + // and we dynamically load inside Views. + // But to keep it simple, let's just keep Views.vue responsible for it. + component: { + // A simple dummy component that just displays a + // This can also be done by making Views handle directly. + // But let's assume Views.vue will have the . + render(h: any) { return h('div', [h('router-view')]) }, + }, + // used to watch for changes in the views modal + // otherwise it wont reload the component + meta: { watchParam: 'id' }, + }, + ], + + }, + ], +}) + +export default router diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts index b93ce22..f542449 100644 --- a/src/shims-vue.d.ts +++ b/src/shims-vue.d.ts @@ -1,4 +1,4 @@ -declare module '*.vue' { - import Vue from 'vue' - export default Vue -} +declare module '*.vue' { + import Vue from 'vue' + export default Vue +} diff --git a/src/store/modules/besluiten.spec.ts b/src/store/modules/besluiten.spec.ts index 551872f..5ec5ba2 100644 --- a/src/store/modules/besluiten.spec.ts +++ b/src/store/modules/besluiten.spec.ts @@ -1,22 +1,22 @@ -/* eslint-disable no-console */ -import { setActivePinia, createPinia } from 'pinia' - -import { useBesluitStore } from './besluiten.js' -import { Besluit, mockBesluit } from '../../entities/index.js' - -describe('Besluit Store', () => { - beforeEach(() => { - setActivePinia(createPinia()) - }) - - it('sets besluit item correctly', () => { - const store = useBesluitStore() - - store.setBesluitItem(mockBesluit()[0]) - - expect(store.besluitItem).toBeInstanceOf(Besluit) - expect(store.besluitItem).toEqual(mockBesluit()[0]) - - expect(store.besluitItem.validate().success).toBe(true) - }) -}) +/* eslint-disable no-console */ +import { setActivePinia, createPinia } from 'pinia' + +import { useBesluitStore } from './besluiten.js' +import { Besluit, mockBesluit } from '../../entities/index.js' + +describe('Besluit Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('sets besluit item correctly', () => { + const store = useBesluitStore() + + store.setBesluitItem(mockBesluit()[0]) + + expect(store.besluitItem).toBeInstanceOf(Besluit) + expect(store.besluitItem).toEqual(mockBesluit()[0]) + + expect(store.besluitItem.validate().success).toBe(true) + }) +}) diff --git a/src/store/modules/besluiten.ts b/src/store/modules/besluiten.ts index f4d3682..8095aae 100644 --- a/src/store/modules/besluiten.ts +++ b/src/store/modules/besluiten.ts @@ -1,165 +1,165 @@ -import { defineStore } from 'pinia' -import { TBesluit, Besluit } from '../../entities/index.js' - -const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/objects/besluiten' - -type TOptions = { - /** - * Save the besluit item to the store in 'besluitItem' - */ - setItem?: boolean -} - -export const useBesluitStore = defineStore('besluiten', { - state: () => ({ - besluitItem: null, - zaakId: null, - }), - actions: { - setBesluitItem(besluitItem: Besluit | TBesluit) { - this.besluitItem = besluitItem && new Besluit(besluitItem) - console.info('Active besluit item set to ' + besluitItem) - }, - /** - * Fetch a single besluit item by its ID. - * - * @param zaakId - The ID of the zaak the besluit belongs to - * @param id - The ID of the besluit item to fetch. - * @param options - Options for fetching the besluit item. (default: `{}`) - * @throws If the HTTP request fails. - * @return { Promise<{ response: Response, data: TBesluit, entity: Besluit }> } The response, raw data, and entity. - */ - async getBesluit( - id: string, - options: TOptions = {}, - ): Promise<{ response: Response, data: TBesluit, entity: Besluit }> { - if (!id) { - throw new Error('No besluit item to fetch') - } - - const endpoint = `${apiEndpoint}/${id}` - - console.info('Fetching besluit item with id: ' + id) - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.error(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() - const entity = new Besluit(data) - - options.setItem && this.setBesluitItem(data) - - return { response, data, entity } - }, - /** - * Fetch all besluiten. - * - * @param zaakId - Optional ID of the zaak to filter besluiten by - * @throws If the HTTP request fails. - * @return { Promise<{ response: Response, data: TBesluit[], entities: Besluit[] }> } The response, raw data array, and entity array. - */ - async getBesluiten(zaakId: string = null): Promise<{ response: Response, data: TBesluit[], entities: Besluit[] }> { - const params = new URLSearchParams() - if (zaakId) { - params.append('zaak', zaakId) - } - - const queryString = params.toString() - const endpoint = queryString - ? `${apiEndpoint}?${queryString}` - : apiEndpoint - - console.info('Fetching besluiten') - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.error(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results as TBesluit[] - const entities = data.map((item: TBesluit) => new Besluit(item)) - - return { response, data, entities } - }, - /** - * Delete a besluit item from the store. - * - * @param besluitId - The ID of the besluit item to delete. - * @throws If the HTTP request fails. - * @return {Promise<{ response: Response }>} The response from the delete request. - */ - async deleteBesluit(besluitId: string): Promise<{ response: Response }> { - if (!besluitId) { - throw new Error('No besluit item to delete') - } - - const endpoint = `${apiEndpoint}/${besluitId}` - - console.info('Deleting besluit item with id: ' + besluitId) - - const response = await fetch(endpoint, { - method: 'DELETE', - }) - - return { response } - }, - /** - * Save a besluit item to the store. If the besluit item does not have a uuid, it will be created. - * Otherwise, it will be updated. - * - * @param besluitItem - The besluit item to save. - * @param options - Options for saving the besluit item. (default: `{ setItem: true }`) - * @throws If there is no besluit item to save or if the HTTP request fails. - * @return {Promise<{ response: Response, data: TBesluit, entity: Besluit }>} The response, raw data, and entity. - */ - async saveBesluit( - besluitItem: Besluit | TBesluit, - options: TOptions = { setItem: true }, - ): Promise<{ response: Response, data: TBesluit, entity: Besluit }> { - if (!besluitItem) { - throw new Error('No besluit item to save') - } - - const isNewBesluit = !besluitItem?.id - const endpoint = isNewBesluit - ? apiEndpoint - : `${apiEndpoint}/${besluitItem?.id}` - const method = isNewBesluit ? 'POST' : 'PUT' - - console.info('Saving besluit item with id: ' + besluitItem?.id) - - const response = await fetch( - endpoint, - { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(besluitItem), - }, - ) - - if (!response.ok) { - console.error(response) - throw new Error(response.statusText || 'Failed to save besluit') - } - - const data = await response.json() as TBesluit - const entity = new Besluit(data) - - options.setItem && this.setBesluitItem(data) - - return { response, data, entity } - }, - }, -}) +import { defineStore } from 'pinia' +import { TBesluit, Besluit } from '../../entities/index.js' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/objects/besluiten' + +type TOptions = { + /** + * Save the besluit item to the store in 'besluitItem' + */ + setItem?: boolean +} + +export const useBesluitStore = defineStore('besluiten', { + state: () => ({ + besluitItem: null, + zaakId: null, + }), + actions: { + setBesluitItem(besluitItem: Besluit | TBesluit) { + this.besluitItem = besluitItem && new Besluit(besluitItem) + console.info('Active besluit item set to ' + besluitItem) + }, + /** + * Fetch a single besluit item by its ID. + * + * @param zaakId - The ID of the zaak the besluit belongs to + * @param id - The ID of the besluit item to fetch. + * @param options - Options for fetching the besluit item. (default: `{}`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TBesluit, entity: Besluit }> } The response, raw data, and entity. + */ + async getBesluit( + id: string, + options: TOptions = {}, + ): Promise<{ response: Response, data: TBesluit, entity: Besluit }> { + if (!id) { + throw new Error('No besluit item to fetch') + } + + const endpoint = `${apiEndpoint}/${id}` + + console.info('Fetching besluit item with id: ' + id) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Besluit(data) + + options.setItem && this.setBesluitItem(data) + + return { response, data, entity } + }, + /** + * Fetch all besluiten. + * + * @param zaakId - Optional ID of the zaak to filter besluiten by + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TBesluit[], entities: Besluit[] }> } The response, raw data array, and entity array. + */ + async getBesluiten(zaakId: string = null): Promise<{ response: Response, data: TBesluit[], entities: Besluit[] }> { + const params = new URLSearchParams() + if (zaakId) { + params.append('zaak', zaakId) + } + + const queryString = params.toString() + const endpoint = queryString + ? `${apiEndpoint}?${queryString}` + : apiEndpoint + + console.info('Fetching besluiten') + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results as TBesluit[] + const entities = data.map((item: TBesluit) => new Besluit(item)) + + return { response, data, entities } + }, + /** + * Delete a besluit item from the store. + * + * @param besluitId - The ID of the besluit item to delete. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteBesluit(besluitId: string): Promise<{ response: Response }> { + if (!besluitId) { + throw new Error('No besluit item to delete') + } + + const endpoint = `${apiEndpoint}/${besluitId}` + + console.info('Deleting besluit item with id: ' + besluitId) + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + return { response } + }, + /** + * Save a besluit item to the store. If the besluit item does not have a uuid, it will be created. + * Otherwise, it will be updated. + * + * @param besluitItem - The besluit item to save. + * @param options - Options for saving the besluit item. (default: `{ setItem: true }`) + * @throws If there is no besluit item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TBesluit, entity: Besluit }>} The response, raw data, and entity. + */ + async saveBesluit( + besluitItem: Besluit | TBesluit, + options: TOptions = { setItem: true }, + ): Promise<{ response: Response, data: TBesluit, entity: Besluit }> { + if (!besluitItem) { + throw new Error('No besluit item to save') + } + + const isNewBesluit = !besluitItem?.id + const endpoint = isNewBesluit + ? apiEndpoint + : `${apiEndpoint}/${besluitItem?.id}` + const method = isNewBesluit ? 'POST' : 'PUT' + + console.info('Saving besluit item with id: ' + besluitItem?.id) + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(besluitItem), + }, + ) + + if (!response.ok) { + console.error(response) + throw new Error(response.statusText || 'Failed to save besluit') + } + + const data = await response.json() as TBesluit + const entity = new Besluit(data) + + options.setItem && this.setBesluitItem(data) + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/modules/contactmoment.ts b/src/store/modules/contactmoment.ts index 6ae2b14..d35c767 100644 --- a/src/store/modules/contactmoment.ts +++ b/src/store/modules/contactmoment.ts @@ -1,177 +1,177 @@ -/* eslint-disable no-console */ -import { defineStore } from 'pinia' -import { ContactMoment, TContactMoment } from '../../entities/index.js' -import router from '../../router/router' - -const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/contactmomenten' - -export const useContactMomentStore = defineStore('contactmomenten', { - state: () => ({ - contactMomentItem: null as ContactMoment, - contactMomentenList: [] as ContactMoment[], - }), - actions: { - /** - * Set the active contact moment item. - * - * @param contactMomentItem - The contact moment item to set. - */ - setContactMomentItem(contactMomentItem: TContactMoment | ContactMoment) { - this.contactMomentItem = contactMomentItem && new ContactMoment(contactMomentItem) - console.info('Active contactmoment item set to ' + contactMomentItem) - }, - /** - * Set the list of contact moments. - * - * @param contactMomentenList - The list of contact moments to set. - */ - setContactMomentenList(contactMomentenList: TContactMoment[] | ContactMoment[]) { - this.contactMomentenList = contactMomentenList.map( - (contactMomentItem) => new ContactMoment(contactMomentItem), - ) - console.info('Contactmomenten list set to ' + contactMomentenList.length + ' items') - }, - /** - * Refresh the list of contact moments. - * - * @param search - Optional search query to filter the contact moments list. (default: `null`) - * @param notClosed - Optional boolean to filter out closed contact moments from the contact moments list. (default: `false`) - * @throws If the HTTP request fails. - * @return {Promise<{ response: Response, data: TContactMoment[], entities: ContactMoment[] }>} The response, raw data, and entities. - */ - async refreshContactMomentenList(search: string = null, notClosed: boolean = false): Promise<{ response: Response, data: TContactMoment[], entities: ContactMoment[] }> { - let endpoint = apiEndpoint - - if (search !== null && search !== '') { - endpoint = endpoint + '?_search=' + search - } - - if (notClosed) { - if (search !== null && search !== '') { - endpoint = endpoint + '&status=open' - } else { - endpoint = endpoint + '?status=open' - } - } - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.info(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results as TContactMoment[] - const entities = data.map((contactMomentItem) => new ContactMoment(contactMomentItem)) - - this.setContactMomentenList(data) - - return { response, data, entities } - }, - /** - * Fetch a single contact moment by its ID. - * - * @param id - The ID of the contact moment to fetch. - * @throws If the ID is invalid or if the HTTP request fails. - * @return {Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }>} The response, raw data, and entity. - */ - async getContactMoment(id: string | number): Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }> { - if (!id || (typeof id !== 'string' && typeof id !== 'number') || (typeof id === 'string' && id.trim() === '')) { - throw new Error('Invalid ID provided for fetching contact moment') - } - - const endpoint = `${apiEndpoint}/${id}` - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.info(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() as TContactMoment - const entity = new ContactMoment(data) - - this.setContactMomentItem(data) - - return { response, data, entity } - }, - /** - * Delete a contact moment. - * - * @param contactMomentItem - The contact moment item to delete. - * @throws If there is no contact moment item to delete or if the contact moment item does not have an id. - * @throws If the HTTP request fails. - * @return {Promise<{ response: Response }>} The response from the delete request. - */ - async deleteContactMoment(contactMomentItem: ContactMoment): Promise<{ response: Response }> { - if (!contactMomentItem || !contactMomentItem.id) { - throw new Error('No contactmoment item to delete') - } - - const endpoint = `${apiEndpoint}/${contactMomentItem.id}` - - const response = await fetch(endpoint, { - method: 'DELETE', - }) - - if (!response.ok) { - console.info(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - this.refreshContactMomentenList() - router.push({ name: 'dynamic-view', params: { view: 'contactmomenten' } }) - - return { response } - }, - /** - * Save a contact moment to the database. If the contact moment does not have an id, it will be created. - * Otherwise, it will be updated. - * - * @param contactMomentItem - The contact moment item to save. - * @throws If there is no contact moment item to save or if the HTTP request fails. - * @return {Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }>} The response, raw data, and entity. - */ - async saveContactMoment(contactMomentItem: TContactMoment | ContactMoment): Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }> { - if (!contactMomentItem) { - throw new Error('No contactmoment item to save') - } - - const isNewContactMoment = !contactMomentItem.id - const endpoint = isNewContactMoment - ? `${apiEndpoint}` - : `${apiEndpoint}/${contactMomentItem.id}` - const method = isNewContactMoment ? 'POST' : 'PUT' - - const response = await fetch( - endpoint, - { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ ...contactMomentItem }), - }, - ) - - if (!response.ok) { - console.info(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() as TContactMoment - const entity = new ContactMoment(data) - - this.setContactMomentItem(data) - this.refreshContactMomentenList() - router.push({ name: 'dynamic-view', params: { view: 'contactmomenten', id: entity.id } }) - - return { response, data, entity } - }, - }, -}) +/* eslint-disable no-console */ +import { defineStore } from 'pinia' +import { ContactMoment, TContactMoment } from '../../entities/index.js' +import router from '../../router/router' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/contactmomenten' + +export const useContactMomentStore = defineStore('contactmomenten', { + state: () => ({ + contactMomentItem: null as ContactMoment, + contactMomentenList: [] as ContactMoment[], + }), + actions: { + /** + * Set the active contact moment item. + * + * @param contactMomentItem - The contact moment item to set. + */ + setContactMomentItem(contactMomentItem: TContactMoment | ContactMoment) { + this.contactMomentItem = contactMomentItem && new ContactMoment(contactMomentItem) + console.info('Active contactmoment item set to ' + contactMomentItem) + }, + /** + * Set the list of contact moments. + * + * @param contactMomentenList - The list of contact moments to set. + */ + setContactMomentenList(contactMomentenList: TContactMoment[] | ContactMoment[]) { + this.contactMomentenList = contactMomentenList.map( + (contactMomentItem) => new ContactMoment(contactMomentItem), + ) + console.info('Contactmomenten list set to ' + contactMomentenList.length + ' items') + }, + /** + * Refresh the list of contact moments. + * + * @param search - Optional search query to filter the contact moments list. (default: `null`) + * @param notClosed - Optional boolean to filter out closed contact moments from the contact moments list. (default: `false`) + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response, data: TContactMoment[], entities: ContactMoment[] }>} The response, raw data, and entities. + */ + async refreshContactMomentenList(search: string = null, notClosed: boolean = false): Promise<{ response: Response, data: TContactMoment[], entities: ContactMoment[] }> { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + if (notClosed) { + if (search !== null && search !== '') { + endpoint = endpoint + '&status=open' + } else { + endpoint = endpoint + '?status=open' + } + } + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.info(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results as TContactMoment[] + const entities = data.map((contactMomentItem) => new ContactMoment(contactMomentItem)) + + this.setContactMomentenList(data) + + return { response, data, entities } + }, + /** + * Fetch a single contact moment by its ID. + * + * @param id - The ID of the contact moment to fetch. + * @throws If the ID is invalid or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }>} The response, raw data, and entity. + */ + async getContactMoment(id: string | number): Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }> { + if (!id || (typeof id !== 'string' && typeof id !== 'number') || (typeof id === 'string' && id.trim() === '')) { + throw new Error('Invalid ID provided for fetching contact moment') + } + + const endpoint = `${apiEndpoint}/${id}` + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.info(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() as TContactMoment + const entity = new ContactMoment(data) + + this.setContactMomentItem(data) + + return { response, data, entity } + }, + /** + * Delete a contact moment. + * + * @param contactMomentItem - The contact moment item to delete. + * @throws If there is no contact moment item to delete or if the contact moment item does not have an id. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteContactMoment(contactMomentItem: ContactMoment): Promise<{ response: Response }> { + if (!contactMomentItem || !contactMomentItem.id) { + throw new Error('No contactmoment item to delete') + } + + const endpoint = `${apiEndpoint}/${contactMomentItem.id}` + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + if (!response.ok) { + console.info(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + this.refreshContactMomentenList() + router.push({ name: 'dynamic-view', params: { view: 'contactmomenten' } }) + + return { response } + }, + /** + * Save a contact moment to the database. If the contact moment does not have an id, it will be created. + * Otherwise, it will be updated. + * + * @param contactMomentItem - The contact moment item to save. + * @throws If there is no contact moment item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }>} The response, raw data, and entity. + */ + async saveContactMoment(contactMomentItem: TContactMoment | ContactMoment): Promise<{ response: Response, data: TContactMoment, entity: ContactMoment }> { + if (!contactMomentItem) { + throw new Error('No contactmoment item to save') + } + + const isNewContactMoment = !contactMomentItem.id + const endpoint = isNewContactMoment + ? `${apiEndpoint}` + : `${apiEndpoint}/${contactMomentItem.id}` + const method = isNewContactMoment ? 'POST' : 'PUT' + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ ...contactMomentItem }), + }, + ) + + if (!response.ok) { + console.info(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() as TContactMoment + const entity = new ContactMoment(data) + + this.setContactMomentItem(data) + this.refreshContactMomentenList() + router.push({ name: 'dynamic-view', params: { view: 'contactmomenten', id: entity.id } }) + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/modules/medewerkers.js b/src/store/modules/medewerkers.js index ec170eb..3309ace 100644 --- a/src/store/modules/medewerkers.js +++ b/src/store/modules/medewerkers.js @@ -1,214 +1,214 @@ -/* eslint-disable no-console */ -import { defineStore } from 'pinia' -import { Medewerker } from '../../entities/index.js' -import router from '../../router/router.ts' - -const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/medewerkers' - -export const useMedewerkerStore = defineStore('medewerkers', { - state: () => ({ - medewerkerItem: false, - medewerkersList: [], - widgetMedewerkerId: null, - auditTrailItem: null, - }), - actions: { - setMedewerkerItem(medewerkerItem) { - this.medewerkerItem = medewerkerItem && new Medewerker(medewerkerItem) - console.log('Active medewerker item set to ' + medewerkerItem) - }, - setWidgetMedewerkerId(widgetMedewerkerId) { - this.widgetMedewerkerId = widgetMedewerkerId - console.log('Widget medewerker id set to ' + widgetMedewerkerId) - }, - setMedewerkersList(medewerkersList) { - this.medewerkersList = medewerkersList.map( - (medewerkerItem) => new Medewerker(medewerkerItem), - ) - console.log('Medewerkers list set to ' + medewerkersList.length + ' items') - }, - setAuditTrailItem(auditTrailItem) { - this.auditTrailItem = auditTrailItem - }, - /* istanbul ignore next */ // ignore this for Jest until moved into a service - async refreshMedewerkersList(search = null) { - let endpoint = apiEndpoint - - if (search !== null && search !== '') { - endpoint = endpoint + '?_search=' + search - } - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results - const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) - - this.setMedewerkersList(data) - - return { response, data, entities } - }, - - async searchMedewerkers(queryParams = null) { - let endpoint = apiEndpoint - - if (queryParams !== null && queryParams !== '') { - endpoint = endpoint + '?' + queryParams - } - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results - const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) - - this.setMedewerkersList(data) - - return { response, data, entities } - }, - - async searchPersons(search = null) { - let endpoint = apiEndpoint - - endpoint = endpoint + '?type=persoon' - - if (search !== null && search !== '') { - endpoint = endpoint + '&voornaam=' + search - } - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results - const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) - - this.setMedewerkersList(data) - - return { response, data, entities } - }, - - async searchOrganisations(search = null) { - let endpoint = apiEndpoint - - endpoint = endpoint + '?type=organisatie' - - if (search !== null && search !== '') { - endpoint = endpoint + '&bedrijfsnaam=' + search - } - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results - const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) - - this.setMedewerkersList(data) - - return { response, data, entities } - }, - - // New function to get a single medewerker - async getMedewerker(id) { - const endpoint = `${apiEndpoint}/${id}` - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() - const entity = new Medewerker(data) - - this.setMedewerkerItem(data) - - return { response, data, entity } - }, - // Delete a medewerker - async deleteMedewerker(medewerkerItem) { - if (!medewerkerItem.id) { - throw new Error('No medewerker item to delete') - } - - const endpoint = `${apiEndpoint}/${medewerkerItem.id}` - - const response = await fetch(endpoint, { - method: 'DELETE', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - this.refreshMedewerkersList() - router.push({ name: 'dynamic-view', params: { view: 'medewerkers' } }) - - return { response } - }, - // Create or save a medewerker from store - async saveMedewerker(medewerkerItem) { - if (!medewerkerItem) { - throw new Error('No medewerker item to save') - } - - const isNewMedewerker = !medewerkerItem.id - const endpoint = isNewMedewerker - ? `${apiEndpoint}` - : `${apiEndpoint}/${medewerkerItem.id}` - const method = isNewMedewerker ? 'POST' : 'PUT' - - const response = await fetch( - endpoint, - { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(medewerkerItem), - }, - ) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() - const entity = new Medewerker(data) - - this.setMedewerkerItem(data) - this.refreshMedewerkersList() - router.push({ name: 'dynamic-view', params: { view: 'medewerkers', id: entity.id } }) - - return { response, data, entity } - }, - }, -}) +/* eslint-disable no-console */ +import { defineStore } from 'pinia' +import { Medewerker } from '../../entities/index.js' +import router from '../../router/router.ts' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/medewerkers' + +export const useMedewerkerStore = defineStore('medewerkers', { + state: () => ({ + medewerkerItem: false, + medewerkersList: [], + widgetMedewerkerId: null, + auditTrailItem: null, + }), + actions: { + setMedewerkerItem(medewerkerItem) { + this.medewerkerItem = medewerkerItem && new Medewerker(medewerkerItem) + console.log('Active medewerker item set to ' + medewerkerItem) + }, + setWidgetMedewerkerId(widgetMedewerkerId) { + this.widgetMedewerkerId = widgetMedewerkerId + console.log('Widget medewerker id set to ' + widgetMedewerkerId) + }, + setMedewerkersList(medewerkersList) { + this.medewerkersList = medewerkersList.map( + (medewerkerItem) => new Medewerker(medewerkerItem), + ) + console.log('Medewerkers list set to ' + medewerkersList.length + ' items') + }, + setAuditTrailItem(auditTrailItem) { + this.auditTrailItem = auditTrailItem + }, + /* istanbul ignore next */ // ignore this for Jest until moved into a service + async refreshMedewerkersList(search = null) { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results + const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) + + this.setMedewerkersList(data) + + return { response, data, entities } + }, + + async searchMedewerkers(queryParams = null) { + let endpoint = apiEndpoint + + if (queryParams !== null && queryParams !== '') { + endpoint = endpoint + '?' + queryParams + } + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results + const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) + + this.setMedewerkersList(data) + + return { response, data, entities } + }, + + async searchPersons(search = null) { + let endpoint = apiEndpoint + + endpoint = endpoint + '?type=persoon' + + if (search !== null && search !== '') { + endpoint = endpoint + '&voornaam=' + search + } + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results + const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) + + this.setMedewerkersList(data) + + return { response, data, entities } + }, + + async searchOrganisations(search = null) { + let endpoint = apiEndpoint + + endpoint = endpoint + '?type=organisatie' + + if (search !== null && search !== '') { + endpoint = endpoint + '&bedrijfsnaam=' + search + } + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results + const entities = data.map((medewerkerItem) => new Medewerker(medewerkerItem)) + + this.setMedewerkersList(data) + + return { response, data, entities } + }, + + // New function to get a single medewerker + async getMedewerker(id) { + const endpoint = `${apiEndpoint}/${id}` + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Medewerker(data) + + this.setMedewerkerItem(data) + + return { response, data, entity } + }, + // Delete a medewerker + async deleteMedewerker(medewerkerItem) { + if (!medewerkerItem.id) { + throw new Error('No medewerker item to delete') + } + + const endpoint = `${apiEndpoint}/${medewerkerItem.id}` + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + this.refreshMedewerkersList() + router.push({ name: 'dynamic-view', params: { view: 'medewerkers' } }) + + return { response } + }, + // Create or save a medewerker from store + async saveMedewerker(medewerkerItem) { + if (!medewerkerItem) { + throw new Error('No medewerker item to save') + } + + const isNewMedewerker = !medewerkerItem.id + const endpoint = isNewMedewerker + ? `${apiEndpoint}` + : `${apiEndpoint}/${medewerkerItem.id}` + const method = isNewMedewerker ? 'POST' : 'PUT' + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(medewerkerItem), + }, + ) + + if (!response.ok) { + console.log(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Medewerker(data) + + this.setMedewerkerItem(data) + this.refreshMedewerkersList() + router.push({ name: 'dynamic-view', params: { view: 'medewerkers', id: entity.id } }) + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/modules/navigation.ts b/src/store/modules/navigation.ts index bfc2e28..f4f8c0c 100644 --- a/src/store/modules/navigation.ts +++ b/src/store/modules/navigation.ts @@ -1,44 +1,44 @@ -/* eslint-disable no-console */ -import { defineStore } from 'pinia' - -interface NavigationStoreState { - modal: 'zaakForm' | 'viewZaakAuditTrail' | 'widgetZaakForm' | 'addBerichtToZaak' | 'addTaakToZaak' | 'addRolToZaak' | 'contactMomentenForm' | 'deleteContactMoment' | 'zaaktypeForm' | 'deleteZaaktype' | 'viewKlantAuditTrail' | 'viewBerichtAuditTrail' | 'editTaak' | 'viewTaakAuditTrail' | 'viewKlantRegister' | 'editMedewerker' | 'resultaatForm' | 'deleteResultaat' | 'besluitForm' | 'deleteBesluit' | null; - viewModal: 'viewContactMoment'; - dialog: string; - transferData: string; -} - -export const useNavigationStore = defineStore('ui', { - state: () => ({ - // The currently active modal, managed trough the state to ensure that only one modal can be active at the same time - modal: null, - // The currently active view modal, managed trough the state to ensure that only one view modal can be active at the same time - viewModal: null, - // The currently active dialog - dialog: null, - // Any data needed in various models, dialogs, views which cannot be transferred through normal means or without writing bad/excessive code - transferData: null, - } as NavigationStoreState), - actions: { - setModal(modal: NavigationStoreState['modal']) { - this.modal = modal - console.log('Active modal set to ' + modal) - }, - setViewModal(viewModal: NavigationStoreState['viewModal']) { - this.viewModal = viewModal - console.log('Active view modal set to ' + viewModal) - }, - setDialog(dialog: NavigationStoreState['dialog']) { - this.dialog = dialog - console.log('Active dialog set to ' + dialog) - }, - setTransferData(data: NavigationStoreState['transferData']) { - this.transferData = data - }, - getTransferData(): NavigationStoreState['transferData'] { - const tempData = this.transferData - this.transferData = null - return tempData - }, - }, -}) +/* eslint-disable no-console */ +import { defineStore } from 'pinia' + +interface NavigationStoreState { + modal: 'zaakForm' | 'viewZaakAuditTrail' | 'widgetZaakForm' | 'addBerichtToZaak' | 'addTaakToZaak' | 'addRolToZaak' | 'contactMomentenForm' | 'deleteContactMoment' | 'zaaktypeForm' | 'deleteZaaktype' | 'viewKlantAuditTrail' | 'viewBerichtAuditTrail' | 'editTaak' | 'viewTaakAuditTrail' | 'viewKlantRegister' | 'editMedewerker' | 'resultaatForm' | 'deleteResultaat' | 'besluitForm' | 'deleteBesluit' | null; + viewModal: 'viewContactMoment'; + dialog: string; + transferData: string; +} + +export const useNavigationStore = defineStore('ui', { + state: () => ({ + // The currently active modal, managed trough the state to ensure that only one modal can be active at the same time + modal: null, + // The currently active view modal, managed trough the state to ensure that only one view modal can be active at the same time + viewModal: null, + // The currently active dialog + dialog: null, + // Any data needed in various models, dialogs, views which cannot be transferred through normal means or without writing bad/excessive code + transferData: null, + } as NavigationStoreState), + actions: { + setModal(modal: NavigationStoreState['modal']) { + this.modal = modal + console.log('Active modal set to ' + modal) + }, + setViewModal(viewModal: NavigationStoreState['viewModal']) { + this.viewModal = viewModal + console.log('Active view modal set to ' + viewModal) + }, + setDialog(dialog: NavigationStoreState['dialog']) { + this.dialog = dialog + console.log('Active dialog set to ' + dialog) + }, + setTransferData(data: NavigationStoreState['transferData']) { + this.transferData = data + }, + getTransferData(): NavigationStoreState['transferData'] { + const tempData = this.transferData + this.transferData = null + return tempData + }, + }, +}) diff --git a/src/store/modules/resultaten.spec.ts b/src/store/modules/resultaten.spec.ts index c797683..4a1afcb 100644 --- a/src/store/modules/resultaten.spec.ts +++ b/src/store/modules/resultaten.spec.ts @@ -1,36 +1,36 @@ -/* eslint-disable no-console */ -import { setActivePinia, createPinia } from 'pinia' - -import { useZaakStore } from './zaken.js' -import { Zaak, mockZaak } from '../../entities/index.js' - -describe('Zaak Store', () => { - beforeEach(() => { - setActivePinia(createPinia()) - }) - - it('sets zaak item correctly', () => { - const store = useZaakStore() - - store.setZaakItem(mockZaak()[0]) - - expect(store.zaakItem).toBeInstanceOf(Zaak) - expect(store.zaakItem).toEqual(mockZaak()[0]) - - expect(store.zaakItem.validate().success).toBe(true) - }) - - it('sets zaken list correctly', () => { - const store = useZaakStore() - - store.setZakenList(mockZaak()) - - expect(store.zakenList).toHaveLength(mockZaak().length) - - store.zakenList.forEach((item, index) => { - expect(item).toBeInstanceOf(Zaak) - expect(item).toEqual(mockZaak()[index]) - expect(item.validate().success).toBe(true) - }) - }) -}) +/* eslint-disable no-console */ +import { setActivePinia, createPinia } from 'pinia' + +import { useZaakStore } from './zaken.js' +import { Zaak, mockZaak } from '../../entities/index.js' + +describe('Zaak Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('sets zaak item correctly', () => { + const store = useZaakStore() + + store.setZaakItem(mockZaak()[0]) + + expect(store.zaakItem).toBeInstanceOf(Zaak) + expect(store.zaakItem).toEqual(mockZaak()[0]) + + expect(store.zaakItem.validate().success).toBe(true) + }) + + it('sets zaken list correctly', () => { + const store = useZaakStore() + + store.setZakenList(mockZaak()) + + expect(store.zakenList).toHaveLength(mockZaak().length) + + store.zakenList.forEach((item, index) => { + expect(item).toBeInstanceOf(Zaak) + expect(item).toEqual(mockZaak()[index]) + expect(item.validate().success).toBe(true) + }) + }) +}) diff --git a/src/store/modules/resultaten.ts b/src/store/modules/resultaten.ts index 6ed4be4..418e4ad 100644 --- a/src/store/modules/resultaten.ts +++ b/src/store/modules/resultaten.ts @@ -1,174 +1,174 @@ -import { defineStore } from 'pinia' -import { TResultaat, Resultaat } from '../../entities/index.js' - -const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/objects/resultaten' - -type TOptions = { - /** - * Save the resultaat item to the store in 'resultaatItem' - */ - setItem?: boolean -} - -export const useResultaatStore = defineStore('resultaten', { - state: () => ({ - resultaatItem: null, - resultatenList: [], - /** - * Set the zaakId, used when creating a new resultaat on a zaak. - */ - zaakId: null, - }), - actions: { - setResultaatItem(resultaatItem: Resultaat | TResultaat) { - this.resultaatItem = resultaatItem && new Resultaat(resultaatItem) - console.info('Active resultaat item set to ' + resultaatItem) - }, - setResultatenList(resultatenList: Resultaat[] | TResultaat[]) { - this.resultatenList = resultatenList.map( - (resultaatItem) => new Resultaat(resultaatItem), - ) - console.info('Resultaten list set to ' + resultatenList.length + ' items') - }, - /** - * Refresh the list of resultaten items. - * - * @param search - Optional search query to filter the resultaten list. (default: `null`) - * @throws If the HTTP request fails. - * @return { Promise<{ response: Response, data: TResultaat[], entities: Resultaat[] }> } The response, raw data, and entities. - */ - async refreshResultatenList(search: string = null): Promise<{ response: Response, data: TResultaat[], entities: Resultaat[] }> { - let endpoint = apiEndpoint - - if (search !== null && search !== '') { - endpoint = endpoint + '?_search=' + search - } - - console.info('Refreshing resultaten list with search: ' + search) - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.error(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = (await response.json()).results as TResultaat[] - const entities = data.map((resultaatItem: TResultaat) => new Resultaat(resultaatItem)) - - this.setResultatenList(data) - - return { response, data, entities } - }, - /** - * Fetch a single resultaat item by its ID. - * - * @param id - The ID of the resultaat item to fetch. - * @param options - Options for fetching the resultaat item. (default: `{}`) - * @throws If the HTTP request fails. - * @return { Promise<{ response: Response, data: TResultaat, entity: Resultaat }> } The response, raw data, and entity. - */ - async getResultaat( - id: string, - options: TOptions = {}, - ): Promise<{ response: Response, data: TResultaat, entity: Resultaat }> { - const endpoint = `${apiEndpoint}/${id}` - - console.info('Fetching resultaat item with id: ' + id) - - const response = await fetch(endpoint, { - method: 'GET', - }) - - if (!response.ok) { - console.error(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() - const entity = new Resultaat(data) - - options.setItem && this.setResultaatItem(data) - - return { response, data, entity } - }, - /** - * Delete a resultaat item from the store. - * - * @param resultaatItem - The resultaat item to delete. - * @throws If the HTTP request fails. - * @return {Promise<{ response: Response }>} The response from the delete request. - */ - async deleteResultaat(resultaatItem: Resultaat | TResultaat): Promise<{ response: Response }> { - if (!resultaatItem) { - throw new Error('No resultaat item to delete') - } - if (!resultaatItem.id) { - throw new Error('No id for resultaat item to delete') - } - - const endpoint = `${apiEndpoint}/${resultaatItem.id}` - - console.info('Deleting resultaat item with id: ' + resultaatItem.id) - - const response = await fetch(endpoint, { - method: 'DELETE', - }) - - this.refreshResultatenList() - - return { response } - }, - /** - * Save a resultaat item to the store. If the resultaat item does not have a uuid, it will be created. - * Otherwise, it will be updated. - * - * @param resultaatItem - The resultaat item to save. - * @param options - Options for saving the resultaat item. (default: `{ setItem: true }`) - * @throws If there is no resultaat item to save or if the HTTP request fails. - * @return {Promise<{ response: Response, data: TResultaat, entity: Resultaat }>} The response, raw data, and entity. - */ - async saveResultaat( - resultaatItem: Resultaat | TResultaat, - options: TOptions = { setItem: true }, - ): Promise<{ response: Response, data: TResultaat, entity: Resultaat }> { - if (!resultaatItem) { - throw new Error('No resultaat item to save') - } - - const isNewResultaat = !resultaatItem?.id - const endpoint = isNewResultaat - ? `${apiEndpoint}` - : `${apiEndpoint}/${resultaatItem?.id}` - const method = isNewResultaat ? 'POST' : 'PUT' - - console.info('Saving resultaat item with id: ' + resultaatItem?.id) - - const response = await fetch( - endpoint, - { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(resultaatItem), - }, - ) - - if (!response.ok) { - console.error(response) - throw new Error(response.statusText || 'Failed to save resultaat') - } - - const data = await response.json() as TResultaat - const entity = new Resultaat(data) - - options.setItem && this.setResultaatItem(data) - this.refreshResultatenList() - - return { response, data, entity } - }, - }, -}) +import { defineStore } from 'pinia' +import { TResultaat, Resultaat } from '../../entities/index.js' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/objects/resultaten' + +type TOptions = { + /** + * Save the resultaat item to the store in 'resultaatItem' + */ + setItem?: boolean +} + +export const useResultaatStore = defineStore('resultaten', { + state: () => ({ + resultaatItem: null, + resultatenList: [], + /** + * Set the zaakId, used when creating a new resultaat on a zaak. + */ + zaakId: null, + }), + actions: { + setResultaatItem(resultaatItem: Resultaat | TResultaat) { + this.resultaatItem = resultaatItem && new Resultaat(resultaatItem) + console.info('Active resultaat item set to ' + resultaatItem) + }, + setResultatenList(resultatenList: Resultaat[] | TResultaat[]) { + this.resultatenList = resultatenList.map( + (resultaatItem) => new Resultaat(resultaatItem), + ) + console.info('Resultaten list set to ' + resultatenList.length + ' items') + }, + /** + * Refresh the list of resultaten items. + * + * @param search - Optional search query to filter the resultaten list. (default: `null`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TResultaat[], entities: Resultaat[] }> } The response, raw data, and entities. + */ + async refreshResultatenList(search: string = null): Promise<{ response: Response, data: TResultaat[], entities: Resultaat[] }> { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + console.info('Refreshing resultaten list with search: ' + search) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results as TResultaat[] + const entities = data.map((resultaatItem: TResultaat) => new Resultaat(resultaatItem)) + + this.setResultatenList(data) + + return { response, data, entities } + }, + /** + * Fetch a single resultaat item by its ID. + * + * @param id - The ID of the resultaat item to fetch. + * @param options - Options for fetching the resultaat item. (default: `{}`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TResultaat, entity: Resultaat }> } The response, raw data, and entity. + */ + async getResultaat( + id: string, + options: TOptions = {}, + ): Promise<{ response: Response, data: TResultaat, entity: Resultaat }> { + const endpoint = `${apiEndpoint}/${id}` + + console.info('Fetching resultaat item with id: ' + id) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Resultaat(data) + + options.setItem && this.setResultaatItem(data) + + return { response, data, entity } + }, + /** + * Delete a resultaat item from the store. + * + * @param resultaatItem - The resultaat item to delete. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteResultaat(resultaatItem: Resultaat | TResultaat): Promise<{ response: Response }> { + if (!resultaatItem) { + throw new Error('No resultaat item to delete') + } + if (!resultaatItem.id) { + throw new Error('No id for resultaat item to delete') + } + + const endpoint = `${apiEndpoint}/${resultaatItem.id}` + + console.info('Deleting resultaat item with id: ' + resultaatItem.id) + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + this.refreshResultatenList() + + return { response } + }, + /** + * Save a resultaat item to the store. If the resultaat item does not have a uuid, it will be created. + * Otherwise, it will be updated. + * + * @param resultaatItem - The resultaat item to save. + * @param options - Options for saving the resultaat item. (default: `{ setItem: true }`) + * @throws If there is no resultaat item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TResultaat, entity: Resultaat }>} The response, raw data, and entity. + */ + async saveResultaat( + resultaatItem: Resultaat | TResultaat, + options: TOptions = { setItem: true }, + ): Promise<{ response: Response, data: TResultaat, entity: Resultaat }> { + if (!resultaatItem) { + throw new Error('No resultaat item to save') + } + + const isNewResultaat = !resultaatItem?.id + const endpoint = isNewResultaat + ? `${apiEndpoint}` + : `${apiEndpoint}/${resultaatItem?.id}` + const method = isNewResultaat ? 'POST' : 'PUT' + + console.info('Saving resultaat item with id: ' + resultaatItem?.id) + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(resultaatItem), + }, + ) + + if (!response.ok) { + console.error(response) + throw new Error(response.statusText || 'Failed to save resultaat') + } + + const data = await response.json() as TResultaat + const entity = new Resultaat(data) + + options.setItem && this.setResultaatItem(data) + this.refreshResultatenList() + + return { response, data, entity } + }, + }, +}) diff --git a/src/views/Views.vue b/src/views/Views.vue index 86c5cc2..a8d24bf 100644 --- a/src/views/Views.vue +++ b/src/views/Views.vue @@ -1,29 +1,29 @@ - - - + + + diff --git a/src/views/componentMapping.js b/src/views/componentMapping.js index 3e23439..b870561 100644 --- a/src/views/componentMapping.js +++ b/src/views/componentMapping.js @@ -1,31 +1,31 @@ -import BerichtenIndex from './berichten/BerichtenIndex.vue' -import BesluitenIndex from './besluiten/BesluitenIndex.vue' -import SearchIndex from './search/SearchIndex.vue' -import DocumentenIndex from './documenten/DocumentenIndex.vue' -import KlantenIndex from './klanten/KlantenIndex.vue' -import MedewerkerIndex from './medewerkers/MedewerkerIndex.vue' -import ResultatenIndex from './resultaten/ResultatenIndex.vue' -import RollenIndex from './rollen/RollenIndex.vue' -import StatusssenIndex from './statussen/StatussenIndex.vue' -import TakenIndex from './taken/TakenIndex.vue' -import ZaakTypenIndex from './zaakTypen/ZakenTypenIndex.vue' -import ZakenIndex from './zaken/ZakenIndex.vue' -import ContactMomentenIndex from './contactMomenten/ContactMomentenIndex.vue' - -// the keys in this object are the names of the routes in the router -// a.k.a. will be used in the url `/klanten/id` -export const viewComponents = { - berichten: BerichtenIndex, - besluiten: BesluitenIndex, - zoeken: SearchIndex, - documenten: DocumentenIndex, - klanten: KlantenIndex, - medewerkers: MedewerkerIndex, - resultaten: ResultatenIndex, - rollen: RollenIndex, - statussen: StatusssenIndex, - taken: TakenIndex, - zaaktypen: ZaakTypenIndex, - zaken: ZakenIndex, - contactmomenten: ContactMomentenIndex, -} +import BerichtenIndex from './berichten/BerichtenIndex.vue' +import BesluitenIndex from './besluiten/BesluitenIndex.vue' +import SearchIndex from './search/SearchIndex.vue' +import DocumentenIndex from './documenten/DocumentenIndex.vue' +import KlantenIndex from './klanten/KlantenIndex.vue' +import MedewerkerIndex from './medewerkers/MedewerkerIndex.vue' +import ResultatenIndex from './resultaten/ResultatenIndex.vue' +import RollenIndex from './rollen/RollenIndex.vue' +import StatusssenIndex from './statussen/StatussenIndex.vue' +import TakenIndex from './taken/TakenIndex.vue' +import ZaakTypenIndex from './zaakTypen/ZakenTypenIndex.vue' +import ZakenIndex from './zaken/ZakenIndex.vue' +import ContactMomentenIndex from './contactMomenten/ContactMomentenIndex.vue' + +// the keys in this object are the names of the routes in the router +// a.k.a. will be used in the url `/klanten/id` +export const viewComponents = { + berichten: BerichtenIndex, + besluiten: BesluitenIndex, + zoeken: SearchIndex, + documenten: DocumentenIndex, + klanten: KlantenIndex, + medewerkers: MedewerkerIndex, + resultaten: ResultatenIndex, + rollen: RollenIndex, + statussen: StatusssenIndex, + taken: TakenIndex, + zaaktypen: ZaakTypenIndex, + zaken: ZakenIndex, + contactmomenten: ContactMomentenIndex, +} diff --git a/src/views/medewerkers/MedewerkerIndex.vue b/src/views/medewerkers/MedewerkerIndex.vue index 5266a8f..7fb4d69 100644 --- a/src/views/medewerkers/MedewerkerIndex.vue +++ b/src/views/medewerkers/MedewerkerIndex.vue @@ -1,72 +1,72 @@ - - - + + + diff --git a/src/views/resultaten/ZaakResultaten.vue b/src/views/resultaten/ZaakResultaten.vue index ed39e4b..f9dccac 100644 --- a/src/views/resultaten/ZaakResultaten.vue +++ b/src/views/resultaten/ZaakResultaten.vue @@ -1,136 +1,136 @@ - - - - - - + + + + + + diff --git a/src/views/widgets/TakenWidget.vue b/src/views/widgets/TakenWidget.vue index 29ded74..abb33e1 100644 --- a/src/views/widgets/TakenWidget.vue +++ b/src/views/widgets/TakenWidget.vue @@ -1,228 +1,228 @@ - - - - - - + + + + + + diff --git a/webpack.config.js b/webpack.config.js index 62f5006..c7e678d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,56 +1,56 @@ -const path = require('path') -const webpackConfig = require('@nextcloud/webpack-vue-config') - -const buildMode = process.env.NODE_ENV -const isDev = buildMode === 'development' -webpackConfig.devtool = isDev ? 'cheap-source-map' : 'source-map' - -webpackConfig.stats = { - colors: true, - modules: false, -} - -const appId = 'zaakafhandelapp' -webpackConfig.entry = { - main: { - import: path.join(__dirname, 'src', 'main.js'), - filename: appId + '-main.js', - }, - adminSettings: { - import: path.join(__dirname, 'src', 'settings.js'), - filename: appId + '-settings.js', - }, - zakenWidget: { - import: path.join(__dirname, 'src', 'zakenWidget.js'), - filename: appId + '-zakenWidget.js', - }, - takenWidget: { - import: path.join(__dirname, 'src', 'takenWidget.js'), - filename: appId + '-takenWidget.js', - }, - openZakenWidget: { - import: path.join(__dirname, 'src', 'openZakenWidget.js'), - filename: appId + '-openZakenWidget.js', - }, - contactmomentenWidget: { - import: path.join(__dirname, 'src', 'contactmomentenWidget.js'), - filename: appId + '-contactmomentenWidget.js', - }, - personenWidget: { - import: path.join(__dirname, 'src', 'personenWidget.js'), - filename: appId + '-personenWidget.js', - }, - organisatiesWidget: { - import: path.join(__dirname, 'src', 'organisatiesWidget.js'), - filename: appId + '-organisatiesWidget.js', - }, -} - -webpackConfig.resolve = { - extensions: ['.ts', '.js', '.vue', '.json'], - alias: { - '@': path.resolve(__dirname, 'src/'), - }, -} - -module.exports = webpackConfig +const path = require('path') +const webpackConfig = require('@nextcloud/webpack-vue-config') + +const buildMode = process.env.NODE_ENV +const isDev = buildMode === 'development' +webpackConfig.devtool = isDev ? 'cheap-source-map' : 'source-map' + +webpackConfig.stats = { + colors: true, + modules: false, +} + +const appId = 'zaakafhandelapp' +webpackConfig.entry = { + main: { + import: path.join(__dirname, 'src', 'main.js'), + filename: appId + '-main.js', + }, + adminSettings: { + import: path.join(__dirname, 'src', 'settings.js'), + filename: appId + '-settings.js', + }, + zakenWidget: { + import: path.join(__dirname, 'src', 'zakenWidget.js'), + filename: appId + '-zakenWidget.js', + }, + takenWidget: { + import: path.join(__dirname, 'src', 'takenWidget.js'), + filename: appId + '-takenWidget.js', + }, + openZakenWidget: { + import: path.join(__dirname, 'src', 'openZakenWidget.js'), + filename: appId + '-openZakenWidget.js', + }, + contactmomentenWidget: { + import: path.join(__dirname, 'src', 'contactmomentenWidget.js'), + filename: appId + '-contactmomentenWidget.js', + }, + personenWidget: { + import: path.join(__dirname, 'src', 'personenWidget.js'), + filename: appId + '-personenWidget.js', + }, + organisatiesWidget: { + import: path.join(__dirname, 'src', 'organisatiesWidget.js'), + filename: appId + '-organisatiesWidget.js', + }, +} + +webpackConfig.resolve = { + extensions: ['.ts', '.js', '.vue', '.json'], + alias: { + '@': path.resolve(__dirname, 'src/'), + }, +} + +module.exports = webpackConfig From 718883ad6b91893eb96b206f426bc41876d25de9 Mon Sep 17 00:00:00 2001 From: Thijn Date: Fri, 27 Dec 2024 16:41:30 +0100 Subject: [PATCH 10/12] finished document --- src/entities/document/document.mock.ts | 76 +++++ src/entities/document/document.spec.ts | 12 + src/entities/document/document.ts | 185 ++++++++++++ src/entities/document/document.types.ts | 71 +++++ src/entities/document/index.js | 3 + src/entities/index.js | 1 + src/modals/Modals.vue | 7 + src/modals/documenten/DeleteDocument.vue | 100 +++++++ src/modals/documenten/DocumentForm.vue | 353 +++++++++++++++++++++++ src/store/modules/documenten.spec.ts | 36 +++ src/store/modules/documenten.ts | 205 +++++++++++++ src/store/store.js | 3 + src/views/documenten/ZaakDocumenten.vue | 124 ++++---- src/views/zaken/ZaakDetails.vue | 4 +- 14 files changed, 1121 insertions(+), 59 deletions(-) create mode 100644 src/entities/document/document.mock.ts create mode 100644 src/entities/document/document.spec.ts create mode 100644 src/entities/document/document.ts create mode 100644 src/entities/document/document.types.ts create mode 100644 src/entities/document/index.js create mode 100644 src/modals/documenten/DeleteDocument.vue create mode 100644 src/modals/documenten/DocumentForm.vue create mode 100644 src/store/modules/documenten.spec.ts create mode 100644 src/store/modules/documenten.ts diff --git a/src/entities/document/document.mock.ts b/src/entities/document/document.mock.ts new file mode 100644 index 0000000..2af2310 --- /dev/null +++ b/src/entities/document/document.mock.ts @@ -0,0 +1,76 @@ +import { Document } from './document' +import { TDocument } from './document.types' + +export const mockDocumentData = (): TDocument[] => [ + { + id: '15551d6f-44e3-43f3-a9d2-59e583c91eb0', + zaak: '550e8400-e29b-41d4-a716-446655440000', + url: 'https://example.com/documents/15551d6f-44e3-43f3-a9d2-59e583c91eb0', + identificatie: 'DOC-2023-001', + bronorganisatie: '123456789', + creatiedatum: '2023-01-01T00:00:00Z', + titel: 'Example Document', + vertrouwelijkheidaanduiding: 'openbaar', + auteur: 'John Doe', + status: 'definitief', + inhoudIsVervallen: false, + formaat: 'application/pdf', + taal: 'nld', + versie: 1, + beginRegistratie: '2023-01-01T00:00:00Z', + bestandsnaam: 'example.pdf', + inhoud: 'https://example.com/documents/15551d6f-44e3-43f3-a9d2-59e583c91eb0/download', + bestandsomvang: BigInt(1024), + link: 'https://example.com/viewer', + beschrijving: 'An example document', + indicatieGebruiksrecht: true, + verschijningsvorm: 'digitaal', + ondertekening: { + soort: 'digitaal', + datum: '2023-01-01T00:00:00Z', + }, + integriteit: { + algoritme: 'sha_256', + waarde: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + datum: '2023-01-01T00:00:00Z', + }, + informatieobjecttype: 'https://example.com/catalogi/informatieobjecttypen/1', + locked: false, + bestandsdelen: [ + { + url: 'https://example.com/documents/15551d6f-44e3-43f3-a9d2-59e583c91eb0/parts/1', + volgnummer: 1, + omvang: 1024, + voltooid: true, + lock: '', + }, + ], + trefwoorden: ['example', 'test'], + _expand: { + informatieobjecttype: { + url: 'https://example.com/catalogi/informatieobjecttypen/1', + catalogus: 'https://example.com/catalogi/1', + omschrijving: 'Example Document Type', + vertrouwelijkheidaanduiding: 'openbaar', + beginGeldigheid: '2023-01-01', + eindeGeldigheid: null, + beginObject: null, + eindeObject: null, + concept: false, + zaaktypen: 'https://example.com/catalogi/zaaktypen/1', + besluittypen: ['https://example.com/catalogi/besluittypen/1'], + informatieobjectcategorie: 'example', + trefwoorden: ['example'], + omschrijvingGeneriek: { + informatieobjecttypeOmschrijvingGeneriek: 'Example Generic Description', + definitieInformatieobjecttypeOmschrijvingGeneriek: 'An example document type', + herkomstInformatieobjecttypeOmschrijvingGeneriek: 'KING', + hierarchieInformatieobjecttypeOmschrijvingGeneriek: 'parent', + opmerkingInformatieobjecttypeOmschrijvingGeneriek: null, + }, + }, + }, + }, +] + +export const mockDocument = (data: TDocument[] = mockDocumentData()): TDocument[] => data.map(item => new Document(item)) diff --git a/src/entities/document/document.spec.ts b/src/entities/document/document.spec.ts new file mode 100644 index 0000000..3af0ae9 --- /dev/null +++ b/src/entities/document/document.spec.ts @@ -0,0 +1,12 @@ +import { Document } from './document' +import { mockDocumentData } from './document.mock' + +describe('Document Entity', () => { + it('should create a Document entity with full data', () => { + const document = new Document(mockDocumentData()[0]) + + expect(document).toBeInstanceOf(Document) + expect(document).toEqual(mockDocumentData()[0]) + expect(document.validate().success).toBe(true) + }) +}) diff --git a/src/entities/document/document.ts b/src/entities/document/document.ts new file mode 100644 index 0000000..42b339e --- /dev/null +++ b/src/entities/document/document.ts @@ -0,0 +1,185 @@ +import { SafeParseReturnType, z } from 'zod' +import { TDocument } from './document.types' + +export class Document implements TDocument { + + public id: string + public zaak?: string + public url: string + public identificatie?: string + public bronorganisatie: string + public creatiedatum: string + public titel: string + public vertrouwelijkheidaanduiding?: 'openbaar' | 'beperkt_openbaar' | 'intern' | 'zaakvertrouwelijk' | 'vertrouwelijk' | 'confidentieel' | 'geheim' | 'zeer_geheim' + public auteur: string + public status?: 'in_bewerking' | 'ter_vaststelling' | 'definitief' | 'gearchiveerd' + public inhoudIsVervallen?: boolean + public formaat?: string + public taal: string + public versie: number + public beginRegistratie: string + public bestandsnaam?: string + public inhoud?: string + public bestandsomvang?: bigint + public link?: string + public beschrijving?: string + public ontvangstdatum?: string + public verzenddatum?: string + public indicatieGebruiksrecht?: boolean + public verschijningsvorm?: string + public ondertekening?: { + soort: 'analoog' | 'digitaal' | 'pki' + datum: string + } + + public integriteit?: { + algoritme: 'crc_16' | 'crc_32' | 'crc_64' | 'fletcher_4' | 'fletcher_8' | 'fletcher_16' | 'fletcher_32' | 'hmac' | 'md5' | 'sha_1' | 'sha_256' | 'sha_512' | 'sha_3' + waarde: string + datum: string + } + + public informatieobjecttype: string + public locked: boolean + public bestandsdelen: { + url: string + volgnummer: number + omvang: number + voltooid: boolean + lock: string + }[] + + public trefwoorden?: string[] + public _expand?: { + informatieobjecttype: { + url: string + catalogus: string + omschrijving: string + vertrouwelijkheidaanduiding: 'openbaar' | 'beperkt_openbaar' | 'intern' | 'zaakvertrouwelijk' | 'vertrouwelijk' | 'confidentieel' | 'geheim' | 'zeer_geheim' + beginGeldigheid: string + eindeGeldigheid?: string + beginObject?: string + eindeObject?: string + concept: boolean + zaaktypen: string + besluittypen: string[] + informatieobjectcategorie: string + trefwoorden?: string[] + omschrijvingGeneriek: { + informatieobjecttypeOmschrijvingGeneriek: string + definitieInformatieobjecttypeOmschrijvingGeneriek: string + herkomstInformatieobjecttypeOmschrijvingGeneriek: string + hierarchieInformatieobjecttypeOmschrijvingGeneriek: string + opmerkingInformatieobjecttypeOmschrijvingGeneriek?: string + } + } + } + + constructor(source: TDocument) { + this.id = source.id || '' + this.zaak = source.zaak || null + this.url = source.url || '' + this.identificatie = source.identificatie || null + this.bronorganisatie = source.bronorganisatie || '' + this.creatiedatum = source.creatiedatum || '' + this.titel = source.titel || '' + this.vertrouwelijkheidaanduiding = source.vertrouwelijkheidaanduiding || null + this.auteur = source.auteur || '' + this.status = source.status || null + this.inhoudIsVervallen = source.inhoudIsVervallen || null + this.formaat = source.formaat || null + this.taal = source.taal || '' + this.versie = source.versie || 0 + this.beginRegistratie = source.beginRegistratie || '' + this.bestandsnaam = source.bestandsnaam || null + this.inhoud = source.inhoud || null + this.bestandsomvang = source.bestandsomvang || null + this.link = source.link || null + this.beschrijving = source.beschrijving || null + this.ontvangstdatum = source.ontvangstdatum || null + this.verzenddatum = source.verzenddatum || null + this.indicatieGebruiksrecht = source.indicatieGebruiksrecht || null + this.verschijningsvorm = source.verschijningsvorm || null + this.ondertekening = source.ondertekening || null + this.integriteit = source.integriteit || null + this.informatieobjecttype = source.informatieobjecttype || '' + this.locked = source.locked || false + this.bestandsdelen = source.bestandsdelen || [] + this.trefwoorden = source.trefwoorden || null + this._expand = source._expand || null + } + + public validate(): SafeParseReturnType { + const schema = z.object({ + id: z.string(), + zaak: z.string().nullable(), + url: z.string().min(1).max(1000).url(), + identificatie: z.string().max(40).nullable(), + bronorganisatie: z.string().max(9), + creatiedatum: z.string(), + titel: z.string().max(200), + vertrouwelijkheidaanduiding: z.enum(['openbaar', 'beperkt_openbaar', 'intern', 'zaakvertrouwelijk', 'vertrouwelijk', 'confidentieel', 'geheim', 'zeer_geheim']).nullable(), + auteur: z.string().max(200), + status: z.enum(['in_bewerking', 'ter_vaststelling', 'definitief', 'gearchiveerd']).nullable(), + inhoudIsVervallen: z.boolean().nullable(), + formaat: z.string().max(255).nullable(), + taal: z.string().length(3), + versie: z.number(), + beginRegistratie: z.string(), + bestandsnaam: z.string().max(255).nullable(), + inhoud: z.string().url().nullable(), + bestandsomvang: z.bigint().nullable(), + link: z.string().max(200).nullable(), + beschrijving: z.string().max(1000).nullable(), + ontvangstdatum: z.string().nullable(), + verzenddatum: z.string().nullable(), + indicatieGebruiksrecht: z.boolean().nullable(), + verschijningsvorm: z.string().nullable(), + ondertekening: z.object({ + soort: z.enum(['analoog', 'digitaal', 'pki']), + datum: z.string(), + }).nullable(), + integriteit: z.object({ + algoritme: z.enum(['crc_16', 'crc_32', 'crc_64', 'fletcher_4', 'fletcher_8', 'fletcher_16', 'fletcher_32', 'hmac', 'md5', 'sha_1', 'sha_256', 'sha_512', 'sha_3']), + waarde: z.string().max(128), + datum: z.string(), + }).nullable(), + informatieobjecttype: z.string().max(200), + locked: z.boolean(), + bestandsdelen: z.array(z.object({ + url: z.string().min(1).max(1000).url(), + volgnummer: z.number(), + omvang: z.number(), + voltooid: z.boolean(), + lock: z.string(), + })), + trefwoorden: z.array(z.string()).nullable(), + _expand: z.object({ + informatieobjecttype: z.object({ + url: z.string().min(1).max(1000).url(), + catalogus: z.string(), + omschrijving: z.string().max(80), + vertrouwelijkheidaanduiding: z.enum(['openbaar', 'beperkt_openbaar', 'intern', 'zaakvertrouwelijk', 'vertrouwelijk', 'confidentieel', 'geheim', 'zeer_geheim']), + beginGeldigheid: z.string(), + eindeGeldigheid: z.string().nullable(), + beginObject: z.string().nullable(), + eindeObject: z.string().nullable(), + concept: z.boolean(), + zaaktypen: z.string(), + besluittypen: z.array(z.string()), + informatieobjectcategorie: z.string().max(80), + trefwoorden: z.array(z.string().max(30)).nullable(), + omschrijvingGeneriek: z.object({ + informatieobjecttypeOmschrijvingGeneriek: z.string().max(80), + definitieInformatieobjecttypeOmschrijvingGeneriek: z.string().max(255), + herkomstInformatieobjecttypeOmschrijvingGeneriek: z.string().max(12), + hierarchieInformatieobjecttypeOmschrijvingGeneriek: z.string().max(80), + opmerkingInformatieobjecttypeOmschrijvingGeneriek: z.string().max(255).nullable(), + }), + }), + }).nullable(), + }) + + return schema.safeParse(this) + } + +} diff --git a/src/entities/document/document.types.ts b/src/entities/document/document.types.ts new file mode 100644 index 0000000..89a7dc0 --- /dev/null +++ b/src/entities/document/document.types.ts @@ -0,0 +1,71 @@ +// https://vng-realisatie.github.io/gemma-zaken/standaard/documenten/redoc-1.5.0#tag/enkelvoudiginformatieobjecten/operation/enkelvoudiginformatieobject_retrieve + +export type TDocument = { + id: string; + zaak?: string; // zaak id property to link to a zaak, this is not in the gemma-zaken documentation + url: string; // min 1 character, max 1000 characters, string as uri + identificatie?: string; // max 40 characters + bronorganisatie: string; // max 9 characters + creatiedatum: string; // string as iso date time string + titel: string; // max 200 characters + vertrouwelijkheidaanduiding?: 'openbaar' | 'beperkt_openbaar' | 'intern' | 'zaakvertrouwelijk' | 'vertrouwelijk' | 'confidentieel' | 'geheim' | 'zeer_geheim'; + auteur: string; // max 200 characters + status?: 'in_bewerking' | 'ter_vaststelling' | 'definitief' | 'gearchiveerd'; + inhoudIsVervallen?: boolean; + formaat?: string; // max 255 characters + taal: string; // 3 characters, ISO 639-2/B language code + versie: number; + beginRegistratie: string; // string as iso date time string + bestandsnaam?: string; // max 255 characters + inhoud?: string; // string as uri + bestandsomvang?: bigint; // int64 + link?: string; // max 200 characters + beschrijving?: string; // max 1000 characters + ontvangstdatum?: string; // deprecated + verzenddatum?: string; // deprecated + indicatieGebruiksrecht?: boolean; + verschijningsvorm?: string; + ondertekening?: { + soort: 'analoog' | 'digitaal' | 'pki'; + datum: string; // string as iso date time string + }; + integriteit?: { + algoritme: 'crc_16' | 'crc_32' | 'crc_64' | 'fletcher_4' | 'fletcher_8' | 'fletcher_16' | 'fletcher_32' | 'hmac' | 'md5' | 'sha_1' | 'sha_256' | 'sha_512' | 'sha_3'; + waarde: string; // max 128 characters + datum: string; + }; + informatieobjecttype: string; // max 200 characters + locked: boolean; + bestandsdelen: { + url: string; // min 1 character, max 1000 characters, string as uri + volgnummer: number; + omvang: number; + voltooid: boolean; + lock: string; + }[]; + trefwoorden?: string[]; + _expand?: { + informatieobjecttype: { + url: string; // min 1 character, max 1000 characters, string as uri + catalogus: string; + omschrijving: string; // max 80 characters + vertrouwelijkheidaanduiding: 'openbaar' | 'beperkt_openbaar' | 'intern' | 'zaakvertrouwelijk' | 'vertrouwelijk' | 'confidentieel' | 'geheim' | 'zeer_geheim'; + beginGeldigheid: string; // string as iso date time string + eindeGeldigheid?: string; // string as iso date time string + beginObject?: string; // string as iso date time string + eindeObject?: string; // string as iso date time string + concept: boolean; + zaaktypen: string; + besluittypen: string[]; // string as uri, has to be unique + informatieobjectcategorie: string; // max 80 characters + trefwoorden?: string[]; // max 30 characters per string + omschrijvingGeneriek: { + informatieobjecttypeOmschrijvingGeneriek: string; // max 80 characters + definitieInformatieobjecttypeOmschrijvingGeneriek: string; // max 255 characters + herkomstInformatieobjecttypeOmschrijvingGeneriek: string; // max 12 characters + hierarchieInformatieobjecttypeOmschrijvingGeneriek: string; // max 80 characters + opmerkingInformatieobjecttypeOmschrijvingGeneriek?: string; // max 255 characters + } + } + } +} diff --git a/src/entities/document/index.js b/src/entities/document/index.js new file mode 100644 index 0000000..7b565d7 --- /dev/null +++ b/src/entities/document/index.js @@ -0,0 +1,3 @@ +export * from './document.ts' +export * from './document.types.ts' +export * from './document.mock.ts' diff --git a/src/entities/index.js b/src/entities/index.js index 4ba60de..d86674f 100644 --- a/src/entities/index.js +++ b/src/entities/index.js @@ -9,3 +9,4 @@ export * from './medewerkers/index.js' export * from './contactmoment/index.js' export * from './resultaat/index.js' export * from './besluit/index.js' +export * from './document/index.js' diff --git a/src/modals/Modals.vue b/src/modals/Modals.vue index b0d4e03..14dba05 100644 --- a/src/modals/Modals.vue +++ b/src/modals/Modals.vue @@ -39,6 +39,9 @@ import { navigationStore } from '../store/store.js' + + + @@ -67,6 +70,8 @@ import DeleteResultaat from './resultaten/DeleteResultaat.vue' import BesluitForm from './besluiten/BesluitForm.vue' import DeleteBesluit from './besluiten/DeleteBesluit.vue' import ViewContactMoment from './contactMomenten/ViewContactMoment.vue' +import DocumentForm from './documenten/DocumentForm.vue' +import DeleteDocument from './documenten/DeleteDocument.vue' export default { name: 'Modals', @@ -95,6 +100,8 @@ export default { BesluitForm, DeleteBesluit, ViewContactMoment, + DocumentForm, + DeleteDocument, }, } diff --git a/src/modals/documenten/DeleteDocument.vue b/src/modals/documenten/DeleteDocument.vue new file mode 100644 index 0000000..7f8a23a --- /dev/null +++ b/src/modals/documenten/DeleteDocument.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/modals/documenten/DocumentForm.vue b/src/modals/documenten/DocumentForm.vue new file mode 100644 index 0000000..c221857 --- /dev/null +++ b/src/modals/documenten/DocumentForm.vue @@ -0,0 +1,353 @@ + + + + + + + diff --git a/src/store/modules/documenten.spec.ts b/src/store/modules/documenten.spec.ts new file mode 100644 index 0000000..61550cb --- /dev/null +++ b/src/store/modules/documenten.spec.ts @@ -0,0 +1,36 @@ +/* eslint-disable no-console */ +import { setActivePinia, createPinia } from 'pinia' + +import { useDocumentStore } from './documenten.js' +import { Document, mockDocumentData } from '../../entities/index.js' + +describe('Document Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('sets document item correctly', () => { + const store = useDocumentStore() + + store.setDocumentItem(mockDocumentData()[0]) + + expect(store.documentItem).toBeInstanceOf(Document) + expect(store.documentItem).toEqual(mockDocumentData()[0]) + + expect(store.documentItem.validate().success).toBe(true) + }) + + it('sets documenten list correctly', () => { + const store = useDocumentStore() + + store.setDocumentenList(mockDocumentData()) + + expect(store.documentenList).toHaveLength(mockDocumentData().length) + + store.documentenList.forEach((item, index) => { + expect(item).toBeInstanceOf(Document) + expect(item).toEqual(mockDocumentData()[index]) + expect(item.validate().success).toBe(true) + }) + }) +}) diff --git a/src/store/modules/documenten.ts b/src/store/modules/documenten.ts new file mode 100644 index 0000000..cd9c40f --- /dev/null +++ b/src/store/modules/documenten.ts @@ -0,0 +1,205 @@ +import { defineStore } from 'pinia' +import { TDocument, Document } from '../../entities/index.js' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/objects/documenten' + +type TOptions = { + /** + * Save the document item to the store in 'documentItem' + */ + setItem?: boolean +} + +export const useDocumentStore = defineStore('documenten', { + state: () => ({ + documentItem: null, + documentenList: [], + /** + * Set the zaakId, used when creating a new document on a zaak. + */ + zaakId: null, + }), + actions: { + setDocumentItem(documentItem: Document | TDocument) { + this.documentItem = documentItem && new Document(documentItem) + console.info('Active document item set to ' + documentItem) + }, + setDocumentenList(documentenList: Document[] | TDocument[]) { + this.documentenList = documentenList.map( + (documentItem) => new Document(documentItem), + ) + console.info('Documenten list set to ' + documentenList.length + ' items') + }, + /** + * Refresh the list of documenten items. + * + * @param search - Optional search query to filter the documenten list. (default: `null`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TDocument[], entities: Document[] }> } The response, raw data, and entities. + */ + async refreshDocumentenList(search: string = null): Promise<{ response: Response, data: TDocument[], entities: Document[] }> { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + console.info('Refreshing documenten list with search: ' + search) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results as TDocument[] + const entities = data.map((documentItem: TDocument) => new Document(documentItem)) + + this.setDocumentenList(data) + + return { response, data, entities } + }, + /** + * Fetch a single document item by its ID. + * + * @param id - The ID of the document item to fetch. + * @param options - Options for fetching the document item. (default: `{}`) + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TDocument, entity: Document }> } The response, raw data, and entity. + */ + async getDocument( + id: string, + options: TOptions = {}, + ): Promise<{ response: Response, data: TDocument, entity: Document }> { + const endpoint = `${apiEndpoint}/${id}` + + console.info('Fetching document item with id: ' + id) + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + const entity = new Document(data) + + options.setItem && this.setDocumentItem(data) + + return { response, data, entity } + }, + /** + * Fetch all documenten. + * + * @param zaakId - Optional ID of the zaak to filter documenten by + * @throws If the HTTP request fails. + * @return { Promise<{ response: Response, data: TDocument[], entities: Document[] }> } The response, raw data array, and entity array. + */ + async getDocumenten(zaakId: string = null): Promise<{ response: Response, data: TDocument[], entities: Document[] }> { + const params = new URLSearchParams() + if (zaakId) { + params.append('zaak', zaakId) + } + + const queryString = params.toString() + const endpoint = queryString + ? `${apiEndpoint}?${queryString}` + : apiEndpoint + + console.info('Fetching documenten') + + const response = await fetch(endpoint, { + method: 'GET', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = (await response.json()).results as TDocument[] + const entities = data.map((item: TDocument) => new Document(item)) + + return { response, data, entities } + }, + /** + * Delete a document item from the store. + * + * @param id - The ID of the document item to delete. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteDocument(id: string): Promise<{ response: Response }> { + if (!id) { + throw new Error('No id for document item to delete') + } + + const endpoint = `${apiEndpoint}/${id}` + + console.info('Deleting document item with id: ' + id) + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + this.refreshDocumentenList() + + return { response } + }, + /** + * Save a document item to the store. If the document item does not have a uuid, it will be created. + * Otherwise, it will be updated. + * + * @param documentItem - The document item to save. + * @param options - Options for saving the document item. (default: `{ setItem: true }`) + * @throws If there is no document item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TDocument, entity: Document }>} The response, raw data, and entity. + */ + async saveDocument( + documentItem: Document | TDocument, + options: TOptions = { setItem: true }, + ): Promise<{ response: Response, data: TDocument, entity: Document }> { + if (!documentItem) { + throw new Error('No document item to save') + } + + const isNewDocument = !documentItem?.id + const endpoint = isNewDocument + ? `${apiEndpoint}` + : `${apiEndpoint}/${documentItem?.id}` + const method = isNewDocument ? 'POST' : 'PUT' + + console.info('Saving document item with id: ' + documentItem?.id) + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(documentItem), + }, + ) + + if (!response.ok) { + console.error(response) + throw new Error(response.statusText || 'Failed to save document') + } + + const data = await response.json() as TDocument + const entity = new Document(data) + + options.setItem && this.setDocumentItem(data) + this.refreshDocumentenList() + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/store.js b/src/store/store.js index d2c2fce..4c57c68 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -12,6 +12,7 @@ import { useContactMomentStore } from './modules/contactmoment.ts' import { useMedewerkerStore } from './modules/medewerkers.js' import { useResultaatStore } from './modules/resultaten.ts' import { useBesluitStore } from './modules/besluiten.ts' +import { useDocumentStore } from './modules/documenten.ts' const berichtStore = useBerichtStore(pinia) const klantStore = useKlantStore(pinia) @@ -25,6 +26,7 @@ const contactMomentStore = useContactMomentStore(pinia) const medewerkerStore = useMedewerkerStore(pinia) const resultaatStore = useResultaatStore(pinia) const besluitStore = useBesluitStore(pinia) +const documentStore = useDocumentStore(pinia) export { berichtStore, @@ -39,4 +41,5 @@ export { medewerkerStore, resultaatStore, besluitStore, + documentStore, } diff --git a/src/views/documenten/ZaakDocumenten.vue b/src/views/documenten/ZaakDocumenten.vue index 1c3208e..e2ea81a 100644 --- a/src/views/documenten/ZaakDocumenten.vue +++ b/src/views/documenten/ZaakDocumenten.vue @@ -1,63 +1,69 @@ + + diff --git a/src/views/zaken/ZaakDetails.vue b/src/views/zaken/ZaakDetails.vue index 5ea06f2..3650023 100644 --- a/src/views/zaken/ZaakDetails.vue +++ b/src/views/zaken/ZaakDetails.vue @@ -1,5 +1,5 @@ Bewerken - + From 03898dc09d64f98d0a64a2ac60605167643ba6cb Mon Sep 17 00:00:00 2001 From: Thijn Date: Mon, 30 Dec 2024 18:18:55 +0100 Subject: [PATCH 11/12] added view zaaktype button --- src/modals/zaken/ZaakForm.vue | 4 ++-- src/views/zaken/ZaakDetails.vue | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/modals/zaken/ZaakForm.vue b/src/modals/zaken/ZaakForm.vue index a13aa2d..462c30b 100644 --- a/src/modals/zaken/ZaakForm.vue +++ b/src/modals/zaken/ZaakForm.vue @@ -203,12 +203,12 @@ export default { this.zaakType = { options: entities.map((zaakType) => ({ id: zaakType.id, - label: zaakType.name, + label: zaakType.identificatie, })), value: selectedZaakType ? { id: selectedZaakType.id, - label: selectedZaakType.name, + label: selectedZaakType.identificatie, } : null, } diff --git a/src/views/zaken/ZaakDetails.vue b/src/views/zaken/ZaakDetails.vue index 4938120..6ecfb42 100644 --- a/src/views/zaken/ZaakDetails.vue +++ b/src/views/zaken/ZaakDetails.vue @@ -1,5 +1,5 @@