From 8b086c09eed2c30fbd7e2e1e0a00a99993449094 Mon Sep 17 00:00:00 2001 From: Thijn Date: Fri, 29 Nov 2024 16:15:54 +0100 Subject: [PATCH 1/2] added ability to close contact moment --- img/progress-close-dark.svg | 5 + img/progress-close-light.svg | 5 + .../contactmoment/contactmoment.mock.ts | 1 + src/entities/contactmoment/contactmoment.ts | 3 + .../contactmoment/contactmoment.types.ts | 1 + src/store/modules/contactmoment.js | 125 ------------- ...ctmoment.spec.js => contactmoment.spec.ts} | 6 +- src/store/modules/contactmoment.ts | 165 ++++++++++++++++++ src/store/store.js | 2 +- src/views/widgets/ContactMomentenWidget.vue | 53 +++++- 10 files changed, 236 insertions(+), 130 deletions(-) create mode 100644 img/progress-close-dark.svg create mode 100644 img/progress-close-light.svg delete mode 100644 src/store/modules/contactmoment.js rename src/store/modules/{contactmoment.spec.js => contactmoment.spec.ts} (83%) create mode 100644 src/store/modules/contactmoment.ts 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/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/store.js b/src/store/store.js index 4009d31..6a0febb 100644 --- a/src/store/store.js +++ b/src/store/store.js @@ -8,7 +8,7 @@ import { useKlantStore } from './modules/klanten.js' import { useRolStore } from './modules/rol.js' 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/widgets/ContactMomentenWidget.vue b/src/views/widgets/ContactMomentenWidget.vue index 8f52713..9911108 100644 --- a/src/views/widgets/ContactMomentenWidget.vue +++ b/src/views/widgets/ContactMomentenWidget.vue @@ -7,7 +7,9 @@ import { navigationStore, contactMomentStore, klantStore } from '../../store/sto
+ :item-menu="itemMenu" + @show="onShow" + @sluiten="onSluiten">