diff --git a/.github/workflows/release-workflow(nightly).yaml b/.github/workflows/release-workflow(nightly).yaml index d5a165c..6e21603 100644 --- a/.github/workflows/release-workflow(nightly).yaml +++ b/.github/workflows/release-workflow(nightly).yaml @@ -35,6 +35,7 @@ jobs: echo "VERSION=${nightly_version}" >> $GITHUB_ENV else echo "VERSION=${current_version}" >> $GITHUB_ENV + fi # - name: Update version in info.xml # run: | @@ -186,8 +187,7 @@ jobs: appstore_token: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} download_url: https://github.com/${{ github.repository }}/releases/download/v${{ env.NEW_VERSION }}/${{ env.APP_NAME }}-${{ env.NEW_VERSION }}.tar.gz app_private_key: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} - nightly: false - + nightly: true - name: Verify version and contents run: | echo "App version: ${{ env.NEW_VERSION }}" diff --git a/img/progress-close-dark.svg b/img/progress-close-dark.svg new file mode 100644 index 0000000..b1e4861 --- /dev/null +++ b/img/progress-close-dark.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/img/progress-close-light.svg b/img/progress-close-light.svg new file mode 100644 index 0000000..e249746 --- /dev/null +++ b/img/progress-close-light.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/entities/contactmoment/contactmoment.mock.ts b/src/entities/contactmoment/contactmoment.mock.ts index 93b2cce..f2fa3ff 100644 --- a/src/entities/contactmoment/contactmoment.mock.ts +++ b/src/entities/contactmoment/contactmoment.mock.ts @@ -12,6 +12,7 @@ export const mockContactMomentData = (): TContactMoment[] => [ taak: 'Taak 3', product: 'Product 3', startDate: new Date().toISOString(), + status: 'open', }, ] diff --git a/src/entities/contactmoment/contactmoment.ts b/src/entities/contactmoment/contactmoment.ts index 423fb62..30d5d1c 100644 --- a/src/entities/contactmoment/contactmoment.ts +++ b/src/entities/contactmoment/contactmoment.ts @@ -12,6 +12,7 @@ export class ContactMoment implements TContactMoment { public taak: string public product: string public startDate: string + public status: string constructor(source: TContactMoment) { this.id = source.id || '' @@ -23,6 +24,7 @@ export class ContactMoment implements TContactMoment { this.taak = source.taak || '' this.product = source.product || '' this.startDate = source.startDate || '' + this.status = source.status || 'open' } public validate(): SafeParseReturnType { @@ -36,6 +38,7 @@ export class ContactMoment implements TContactMoment { taak: z.string().min(1), product: z.string().min(1), startDate: z.string().min(1), + status: z.string().min(1), }) return schema.safeParse(this) diff --git a/src/entities/contactmoment/contactmoment.types.ts b/src/entities/contactmoment/contactmoment.types.ts index e6e8107..4c7e790 100644 --- a/src/entities/contactmoment/contactmoment.types.ts +++ b/src/entities/contactmoment/contactmoment.types.ts @@ -8,4 +8,5 @@ export type TContactMoment = { taak: string; product: string; startDate: string; + status: string; } diff --git a/src/entities/klanten/klanten.mock.ts b/src/entities/klanten/klanten.mock.ts index 4642e10..842277e 100644 --- a/src/entities/klanten/klanten.mock.ts +++ b/src/entities/klanten/klanten.mock.ts @@ -10,6 +10,7 @@ export const mockKlantData = (): TKlant[] => [ achternaam: 'Doe', bsn: '1234567890', geboortedatum: '1990-01-01', + isMale: true, land: 'Nederland', telefoonnummer: '0612345678', emailadres: 'john.doe@example.com', diff --git a/src/entities/klanten/klanten.ts b/src/entities/klanten/klanten.ts index 4675a7c..b3f7c4e 100644 --- a/src/entities/klanten/klanten.ts +++ b/src/entities/klanten/klanten.ts @@ -11,6 +11,7 @@ export class Klant implements TKlant { public achternaam: string public bsn: string public geboortedatum: string + public isMale: boolean public land: string public telefoonnummer: string @@ -41,6 +42,7 @@ export class Klant implements TKlant { this.achternaam = source.achternaam || '' this.bsn = source.bsn || '' this.geboortedatum = source.geboortedatum || '' + this.isMale = source.isMale || false this.land = source.land || '' this.telefoonnummer = source.telefoonnummer || '' this.emailadres = source.emailadres || '' @@ -69,6 +71,7 @@ export class Klant implements TKlant { achternaam: z.string(), bsn: z.string(), geboortedatum: z.string(), + isMale: z.boolean(), land: z.string(), telefoonnummer: z.string(), emailadres: z.string().email(), diff --git a/src/entities/klanten/klanten.types.ts b/src/entities/klanten/klanten.types.ts index 2c59511..83c11bc 100644 --- a/src/entities/klanten/klanten.types.ts +++ b/src/entities/klanten/klanten.types.ts @@ -8,6 +8,7 @@ export type TKlant = { achternaam: string; bsn: string; geboortedatum: string; + isMale: boolean; land: string; telefoonnummer: string; emailadres: string; diff --git a/src/entities/rol/rol.mock.ts b/src/entities/rol/rol.mock.ts index 0711729..736750e 100644 --- a/src/entities/rol/rol.mock.ts +++ b/src/entities/rol/rol.mock.ts @@ -3,6 +3,7 @@ import { TRol } from './rol.types' export const mockRolData = (): TRol[] => [ { + id: '1', uuid: '15551d6f-44e3-43f3-a9d2-59e583c91eb0', omschrijving: 'Zaak 3', omschrijvingGeneriek: 'Zaak 3', diff --git a/src/entities/rol/rol.ts b/src/entities/rol/rol.ts index 04692b0..a76fb23 100644 --- a/src/entities/rol/rol.ts +++ b/src/entities/rol/rol.ts @@ -3,6 +3,7 @@ import { TRol } from './rol.types' export class Rol implements TRol { + public id: string public uuid: string public omschrijving: string public omschrijvingGeneriek: string @@ -37,6 +38,7 @@ export class Rol implements TRol { } constructor(source: TRol) { + this.id = source.id || null this.uuid = source.uuid || '' this.omschrijving = source.omschrijving || '' this.omschrijvingGeneriek = source.omschrijvingGeneriek || '' @@ -57,6 +59,7 @@ export class Rol implements TRol { public validate(): SafeParseReturnType { const schema = z.object({ + id: z.string().optional(), uuid: z.string().optional(), omschrijving: z.string().min(1), omschrijvingGeneriek: z.string(), diff --git a/src/entities/rol/rol.types.ts b/src/entities/rol/rol.types.ts index a97aacc..e0fbff9 100644 --- a/src/entities/rol/rol.types.ts +++ b/src/entities/rol/rol.types.ts @@ -1,4 +1,5 @@ export type TRol = { + id?: string; uuid: string; omschrijving: string; omschrijvingGeneriek: string; diff --git a/src/entities/taak/taak.mock.ts b/src/entities/taak/taak.mock.ts index 730b4e4..979f181 100644 --- a/src/entities/taak/taak.mock.ts +++ b/src/entities/taak/taak.mock.ts @@ -8,6 +8,7 @@ export const mockTaakData = (): TTaak[] => [ zaak: '15551d6f-44e3-43f3-a9d2-59e583c91eb0', type: 'Audit', status: 'verwerkt', + deadline: '2024-12-01T00:00:00.000Z', onderwerp: 'Uitvoering van een interne audit om de naleving van kwaliteitsnormen te controleren.', 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.', actie: 'Voorbereiden van auditchecklist, uitvoeren van audits, rapporteren van bevindingen, aanbevelen van verbeteringen.', diff --git a/src/entities/taak/taak.ts b/src/entities/taak/taak.ts index f7b2def..8f75c80 100644 --- a/src/entities/taak/taak.ts +++ b/src/entities/taak/taak.ts @@ -1,5 +1,6 @@ import { SafeParseReturnType, z } from 'zod' import { TTaak, ZaakID } from './taak.types' +import getValidISOstring from '../../services/getValidISOstring' export class Taak implements TTaak { @@ -8,6 +9,7 @@ export class Taak implements TTaak { public zaak: ZaakID public type: string public status: string + public deadline: string public onderwerp: string public toelichting: string public actie: string @@ -19,6 +21,7 @@ export class Taak implements TTaak { this.zaak = source.zaak || '' this.type = source.type || '' this.status = source.status || '' + this.deadline = source.deadline ? getValidISOstring(source.deadline) : null this.onderwerp = source.onderwerp || '' this.toelichting = source.toelichting || '' this.actie = source.actie || '' @@ -32,6 +35,7 @@ export class Taak implements TTaak { zaak: z.string().min(1), type: z.string().min(1), status: z.string().min(1), + deadline: z.string().datetime().nullable(), onderwerp: z.string().min(1), toelichting: z.string(), actie: z.string(), diff --git a/src/entities/taak/taak.types.ts b/src/entities/taak/taak.types.ts index fa79c89..141bd6f 100644 --- a/src/entities/taak/taak.types.ts +++ b/src/entities/taak/taak.types.ts @@ -6,6 +6,7 @@ export type TTaak = { zaak: ZaakID; type: string; status: string; + deadline: string; onderwerp: string; toelichting: string; actie: string; diff --git a/src/modals/contactMomenten/ContactMomentenForm.vue b/src/modals/contactMomenten/ContactMomentenForm.vue index 4fa8a57..1dfafee 100644 --- a/src/modals/contactMomenten/ContactMomentenForm.vue +++ b/src/modals/contactMomenten/ContactMomentenForm.vue @@ -29,7 +29,7 @@ import { navigationStore } from '../../store/store.js' -
+
+ +
+
+ + + Klant ontkoppelen + +
+
+
response.json()) .then(data => { this.klant = data - return fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/zaken`) }) + .catch(error => { + console.error('Error fetching klant:', error) + throw error // if this one fails, fetching the rest is pointless + }) + + fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/zaken`) .then(response => response.json()) .then(data => { if (Array.isArray(data.results)) { this.zaken = data.results } - return fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/taken`) }) + .catch(error => { + console.error('Error fetching klant zaken:', error) + }) + + fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/taken`) .then(response => response.json()) .then(data => { if (Array.isArray(data.results)) { this.taken = data.results } - return fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/berichten`) }) + .catch(error => { + console.error('Error fetching klant taken:', error) + }) + + fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/berichten`) .then(response => response.json()) .then(data => { if (Array.isArray(data.results)) { this.berichten = data.results } - return fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/audit_trail`) }) + .catch(error => { + console.error('Error fetching klant berichten:', error) + }) + + fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/audit_trail`) .then(response => response.json()) .then(data => { if (Array.isArray(data)) { @@ -352,7 +385,7 @@ export default { } }) .catch(error => { - console.error('Error fetching klant data:', error) + console.error('Error fetching klant audit trail:', error) }) }, diff --git a/src/modals/klantRegister/ViewKlantRegister.vue b/src/modals/klantRegister/ViewKlantRegister.vue index b742149..ea65707 100644 --- a/src/modals/klantRegister/ViewKlantRegister.vue +++ b/src/modals/klantRegister/ViewKlantRegister.vue @@ -12,8 +12,8 @@ import { taakStore, navigationStore, zaakStore, klantStore } from '../../store/s
- Klantnummer: -

{{ klant.klantnummer || '-' }}

+ KVK nummer: +

{{ klant.kvkNummer || '-' }}

@@ -26,7 +26,7 @@ import { taakStore, navigationStore, zaakStore, klantStore } from '../../store/s
Adres: -

{{ klant.adres || '-' }}

+

{{ `${klant.straatnaam} ${klant.huisnummer} ${klant.postcode} ${klant.plaats}` || '-' }}

Functie: @@ -295,15 +295,23 @@ export default { if (Array.isArray(data.results)) { this.zaken = data.results } - return fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/taken`) }) + .catch(error => { + console.error('Error fetching zaken:', error) + }) + + fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/taken`) .then(response => response.json()) .then(data => { if (Array.isArray(data.results)) { this.taken = data.results } - return fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/berichten`) }) + .catch(error => { + console.error('Error fetching taken:', error) + }) + + fetch(`/index.php/apps/zaakafhandelapp/api/klanten/${id}/berichten`) .then(response => response.json()) .then(data => { if (Array.isArray(data.results)) { @@ -311,7 +319,7 @@ export default { } }) .catch(error => { - console.error('Error fetching klant data:', error) + console.error('Error fetching berichten:', error) }) }, diff --git a/src/modals/klanten/EditKlant.vue b/src/modals/klanten/EditKlant.vue index 35db834..7f36202 100644 --- a/src/modals/klanten/EditKlant.vue +++ b/src/modals/klanten/EditKlant.vue @@ -47,6 +47,9 @@ import { klantStore, navigationStore } from '../../store/store.js' :disabled="loading" input-label="Geboortedatum" />
+ + Is een man? + @@ -63,14 +62,30 @@ import { klantStore } from '../../store/store.js'
- +
+ + + +
+
+ +
@@ -121,7 +136,7 @@ import { klantStore } from '../../store/store.js' type="secondary" @click="closeModal()"> Annuleer @@ -130,9 +145,9 @@ import { klantStore } from '../../store/store.js' :disabled="!selectedKlant" @click="addKlant()"> - toevoegen + Koppelen @@ -140,16 +155,17 @@ import { klantStore } from '../../store/store.js' @@ -80,6 +103,9 @@ export default { padding: 0 0 10px 0; margin: 0 0 10px 0; } +.audit-item > *:not(:last-child) { + margin-bottom: 1rem; +} .navigation-buttons { margin-top: 10px; @@ -87,4 +113,26 @@ export default { gap: 10px; justify-content: center; } + +/* Changes Table */ +.audit-trail-table-container { + max-height: 350px; + overflow-y: auto; +} + +.audit-trail-table thead { + position: sticky; + top: 0; + background-color: var(--color-main-background); +} + +.audit-trail-table th { + font-weight: bold; + font-size: 1rem; +} + +.audit-trail-table th, +.audit-trail-table td { + padding: 0.5rem; +} diff --git a/src/services/getTheme.js b/src/services/getTheme.js index bfe63e7..80fc853 100644 --- a/src/services/getTheme.js +++ b/src/services/getTheme.js @@ -12,14 +12,12 @@ export const getTheme = () => { if (document.body.hasAttribute('data-theme-light')) { return 'light' - } - - if (document.body.hasAttribute('data-theme-dark')) { + } else if (document.body.hasAttribute('data-theme-dark')) { + return 'dark' + } else if (window.matchMedia('(prefers-color-scheme: light)').matches) { + return 'light' + } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark' - } - - if (document.body.hasAttribute('data-theme-default')) { - return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark' } return 'light' } diff --git a/src/services/getValidISOstring.js b/src/services/getValidISOstring.js new file mode 100644 index 0000000..231912e --- /dev/null +++ b/src/services/getValidISOstring.js @@ -0,0 +1,20 @@ +/** + * Converts a given date string or Date object to a valid ISO string. + * + * this function can double as a validator for ISO / date strings + * + * If the dateString is valid it will return the ISO string, + * if it is not a valid dateString it will return null. + * + * @param { string | Date } dateString The date string or Date object to be converted. + * @return { string | null } The ISO string representation of the date or null. + */ +export default function getValidISOstring(dateString) { + const date = new Date(dateString) + + if (!isNaN(date.getTime())) { + return date.toISOString() + } else { + return null + } +} diff --git a/src/store/modules/contactmoment.js b/src/store/modules/contactmoment.js deleted file mode 100644 index f155ef3..0000000 --- a/src/store/modules/contactmoment.js +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable no-console */ -import { defineStore } from 'pinia' -import { ContactMoment } from '../../entities/index.js' - -const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/contactmomenten' - -export const useContactMomentStore = defineStore('contactmomenten', { - state: () => ({ - contactMomentItem: false, - contactMomentenList: [], - }), - actions: { - setContactMomentItem(contactMomentItem) { - this.contactMomentItem = contactMomentItem && new ContactMoment(contactMomentItem) - console.info('Active contactmoment item set to ' + contactMomentItem) - }, - setContactMomentenList(contactMomentenList) { - this.contactMomentenList = contactMomentenList.map( - (contactMomentItem) => new ContactMoment(contactMomentItem), - ) - console.info('Contactmomenten list set to ' + contactMomentenList.length + ' items') - }, - /* istanbul ignore next */ // ignore this for Jest until moved into a service - async refreshContactMomentenList(search = null) { - let endpoint = apiEndpoint - - if (search !== null && search !== '') { - endpoint = endpoint + '?_search=' + search - } - - 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 - const entities = data.map((contactMomentItem) => new ContactMoment(contactMomentItem)) - - this.setContactMomentenList(data) - - return { response, data, entities } - }, - // New function to get a single contactmoment - async getContactMoment(id) { - 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() - const entity = new ContactMoment(data) - - this.setContactMomentItem(data) - - return { response, data, entity } - }, - // Delete a contactmoment - async deleteContactMoment(contactMomentItem) { - if (!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() - - return { response } - }, - // Create or save a contactmoment from store - async saveContactMoment(contactMomentItem) { - 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() - const entity = new ContactMoment(data) - - this.setContactMomentItem(data) - this.refreshContactMomentenList() - - return { response, data, entity } - }, - }, -}) diff --git a/src/store/modules/contactmoment.spec.js b/src/store/modules/contactmoment.spec.ts similarity index 83% rename from src/store/modules/contactmoment.spec.js rename to src/store/modules/contactmoment.spec.ts index efe8eeb..bac1ba4 100644 --- a/src/store/modules/contactmoment.spec.js +++ b/src/store/modules/contactmoment.spec.ts @@ -23,11 +23,11 @@ describe('Contact Moment Store', () => { it('sets contact moment list correctly', () => { const store = useContactMomentStore() - store.setContactMomentList(mockContactMoment()) + store.setContactMomentenList(mockContactMoment()) - expect(store.contactMomentList).toHaveLength(mockContactMoment().length) + expect(store.contactMomentenList).toHaveLength(mockContactMoment().length) - store.contactMomentList.forEach((item, index) => { + store.contactMomentenList.forEach((item, index) => { expect(item).toBeInstanceOf(ContactMoment) expect(item).toEqual(mockContactMoment()[index]) expect(item.validate().success).toBe(true) diff --git a/src/store/modules/contactmoment.ts b/src/store/modules/contactmoment.ts new file mode 100644 index 0000000..e0a0b00 --- /dev/null +++ b/src/store/modules/contactmoment.ts @@ -0,0 +1,165 @@ +/* eslint-disable no-console */ +import { defineStore } from 'pinia' +import { ContactMoment, TContactMoment } from '../../entities/index.js' + +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`) + * @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): Promise<{ response: Response, data: TContactMoment[], entities: ContactMoment[] }> { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + 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() + + 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() + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/modules/rol.js b/src/store/modules/rol.js deleted file mode 100644 index 9fbc0d9..0000000 --- a/src/store/modules/rol.js +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable no-console */ -import { defineStore } from 'pinia' -import { Rol } from '../../entities/index.js' - -const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/zrc/rollen' - -export const useRolStore = defineStore('rollen', { - state: () => ({ - rolItem: false, - rollenList: [], - }), - actions: { - setRolItem(rolItem) { - this.rolItem = rolItem && new Rol(rolItem) - console.log('Active rol item set to ' + rolItem) - }, - setRollenList(rollenList) { - this.rollenList = rollenList.map( - (rolItem) => new Rol(rolItem), - ) - console.log('Rollen list set to ' + rollenList.length + ' items') - }, - /* istanbul ignore next */ // ignore this for Jest until moved into a service - async refreshRollenList(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((rolItem) => new Rol(rolItem)) - - this.setRollenList(data) - - return { response, data, entities } - }, - // New function to get a single rol - async getRol(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 Rol(data) - - this.setRolItem(data) - - return { response, data, entity } - }, - // Delete a rol - async deleteRol(rolItem) { - if (!rolItem.id) { - throw new Error('No rol item to delete') - } - - const endpoint = `${apiEndpoint}/${rolItem.id}` - - const response = await fetch(endpoint, { - method: 'DELETE', - }) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - this.refreshRollenList() - - return { response } - }, - // Create or save a rol from store - async saveRol(rolItem) { - if (!rolItem) { - throw new Error('No rol item to save') - } - - const isNewRol = !rolItem.id - const endpoint = isNewRol - ? `${apiEndpoint}` - : `${apiEndpoint}/${rolItem.id}` - const method = isNewRol ? 'POST' : 'PUT' - - const response = await fetch( - endpoint, - { - method, - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(rolItem), - }, - ) - - if (!response.ok) { - console.log(response) - throw new Error(`HTTP error! status: ${response.status}`) - } - - const data = await response.json() - const entity = new Rol(data) - - this.setRolItem(data) - this.refreshRollenList() - - return { response, data, entity } - }, - }, -}) diff --git a/src/store/modules/rol.spec.js b/src/store/modules/rol.spec.ts similarity index 96% rename from src/store/modules/rol.spec.js rename to src/store/modules/rol.spec.ts index 4a1afcb..c797683 100644 --- a/src/store/modules/rol.spec.js +++ b/src/store/modules/rol.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/rol.ts b/src/store/modules/rol.ts new file mode 100644 index 0000000..6b68442 --- /dev/null +++ b/src/store/modules/rol.ts @@ -0,0 +1,176 @@ +import { defineStore } from 'pinia' +import { TRol, Rol } from '../../entities/index.js' + +const apiEndpoint = '/index.php/apps/zaakafhandelapp/api/zrc/rollen' + +type TOptions = { + /** + * Save the rol item to the store in 'rolItem' + */ + setItem?: boolean +} + +export const useRolStore = defineStore('rollen', { + state: () => ({ + rolItem: null as Rol, + rollenList: [] as Rol[], + }), + actions: { + setRolItem(rolItem: Rol | TRol) { + this.rolItem = rolItem && new Rol(rolItem) + console.info('Active rol item set to ' + rolItem) + }, + setRollenList(rollenList: Rol[] | TRol[]) { + this.rollenList = rollenList.map( + (rolItem) => new Rol(rolItem), + ) + console.info('Rollen list set to ' + rollenList.length + ' items') + }, + /** + * Refresh the list of rollen items. + * + * @param search - Optional search query to filter the rollen list. (default: `null`) + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response, data: TRol[], entities: Rol[] }>} The response, raw data, and entities. + */ + async refreshRollenList(search: string = null): Promise<{ response: Response, data: TRol[], entities: Rol[] }> { + let endpoint = apiEndpoint + + if (search !== null && search !== '') { + endpoint = endpoint + '?_search=' + search + } + + console.info('Refreshing rollen 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 TRol[] + const entities = data.map((rolItem: TRol) => new Rol(rolItem)) + + this.setRollenList(data) + + return { response, data, entities } + }, + /** + * Fetch a single rol item by its ID. + * + * @param id - The ID of the rol item to fetch. + * @param options - Options for fetching the rol item. (default: `{}`) + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response, data: TRol, entity: Rol }>} The response, raw data, and entity. + */ + async getRol( + id: string, + options: TOptions = {}, + ): Promise<{ response: Response, data: TRol, entity: Rol }> { + const endpoint = `${apiEndpoint}/${id}` + + console.info('Fetching rol 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 Rol(data) + + options.setItem && this.setRolItem(data) + + return { response, data, entity } + }, + /** + * Delete a rol item from the database. + * + * @param rolItem - The rol item to delete. + * @throws If there is no rol item to delete or if the rol item does not have an id. + * @throws If the HTTP request fails. + * @return {Promise<{ response: Response }>} The response from the delete request. + */ + async deleteRol(rolItem: Rol | TRol): Promise<{ response: Response }> { + if (!rolItem) { + throw new Error('No rol item to delete') + } + if (!rolItem.id) { + throw new Error('No id for rol item to delete') + } + + const endpoint = `${apiEndpoint}/${rolItem.id}` + + console.info('Deleting rol item with id: ' + rolItem.id) + + const response = await fetch(endpoint, { + method: 'DELETE', + }) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + this.refreshRollenList() + + return { response } + }, + /** + * Save a rol item to the database. If the rol item does not have an id, it will be created. + * Otherwise, it will be updated. + * + * @param rolItem - The rol item to save. + * @param options - Options for saving the rol item. (default: `{ setItem: true }`) + * @throws If there is no rol item to save or if the HTTP request fails. + * @return {Promise<{ response: Response, data: TRol, entity: Rol }>} The response, raw data, and entity. + */ + async saveRol( + rolItem: Rol | TRol, + options: TOptions = { setItem: true }, + ): Promise<{ response: Response, data: TRol, entity: Rol }> { + if (!rolItem) { + throw new Error('No rol item to save') + } + + const isNewRol = !rolItem.id + const endpoint = isNewRol + ? `${apiEndpoint}` + : `${apiEndpoint}/${rolItem.id}` + const method = isNewRol ? 'POST' : 'PUT' + + console.info('Saving rol item with id: ' + rolItem.id) + + const response = await fetch( + endpoint, + { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(rolItem), + }, + ) + + if (!response.ok) { + console.error(response) + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() as TRol + const entity = new Rol(data) + + options.setItem && this.setRolItem(data) + this.refreshRollenList() + + return { response, data, entity } + }, + }, +}) diff --git a/src/store/store.js b/src/store/store.js index 4009d31..9e2a38a 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -5,10 +5,10 @@ import { useZaakStore } from './modules/zaken.ts' import { useZaakTypeStore } from './modules/zaakTypen.ts' import { useBerichtStore } from './modules/berichten.js' import { useKlantStore } from './modules/klanten.js' -import { useRolStore } from './modules/rol.js' +import { useRolStore } from './modules/rol.ts' import { useTaakStore } from './modules/taak.js' import { useSearchStore } from './modules/search.ts' -import { useContactMomentStore } from './modules/contactmoment.js' +import { useContactMomentStore } from './modules/contactmoment.ts' const berichtStore = useBerichtStore(pinia) const klantStore = useKlantStore(pinia) const navigationStore = useNavigationStore(pinia) diff --git a/src/views/berichten/ZaakBerichten.vue b/src/views/berichten/ZaakBerichten.vue index 711294e..d01a2a3 100644 --- a/src/views/berichten/ZaakBerichten.vue +++ b/src/views/berichten/ZaakBerichten.vue @@ -1,20 +1,20 @@ - +
+ +
+ Geen berichten gevonden. +
- + diff --git a/src/views/documenten/ZaakDocumenten.vue b/src/views/documenten/ZaakDocumenten.vue index 93f7ae1..1c3208e 100644 --- a/src/views/documenten/ZaakDocumenten.vue +++ b/src/views/documenten/ZaakDocumenten.vue @@ -1,25 +1,25 @@ - + + +
+ Geen taken gevonden. +
-import { navigationStore, contactMomentStore } from '../../store/store.js' +import { navigationStore, contactMomentStore, klantStore } from '../../store/store.js'