From 0cdfccb0ca369e87c57d4e7092fcd6c82be832bd Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Mon, 9 Dec 2024 00:48:17 +0100 Subject: [PATCH] Refactor groups API to reuse the abstract API --- app/[slug]/dashboard/guests/page.tsx | 7 +-- app/api/groups.tsx | 81 ------------------------- app/lib/definitions.ts | 29 ++++----- app/lib/group.tsx | 62 +++++++++++++++++++ app/lib/guest.tsx | 4 +- app/ui/components/group-form-dialog.tsx | 13 ++-- app/ui/components/guest-form-dialog.tsx | 2 +- app/ui/groups/table.tsx | 9 ++- 8 files changed, 94 insertions(+), 113 deletions(-) delete mode 100644 app/api/groups.tsx create mode 100644 app/lib/group.tsx diff --git a/app/[slug]/dashboard/guests/page.tsx b/app/[slug]/dashboard/guests/page.tsx index b98e688..beb73f8 100644 --- a/app/[slug]/dashboard/guests/page.tsx +++ b/app/[slug]/dashboard/guests/page.tsx @@ -2,9 +2,7 @@ 'use client'; -import { loadGroups } from '@/app/api/groups'; import { AbstractApi, } from '@/app/api/abstract-api'; -import { Group } from '@/app/lib/definitions'; import { Guest, GuestSerializer } from '@/app/lib/guest'; import { classNames } from '@/app/ui/components/button'; import GroupFormDialog from '@/app/ui/components/group-form-dialog'; @@ -14,6 +12,7 @@ import SkeletonTable from '@/app/ui/guests/skeleton-row'; import GuestsTable from '@/app/ui/guests/table'; import { TabPanel, TabView } from 'primereact/tabview'; import { Suspense, useState } from 'react'; +import { Group, GroupSerializer } from '@/app/lib/group'; export default function Page() { @@ -25,8 +24,8 @@ export default function Page() { } function refreshGroups() { - loadGroups((groups) => { - setGroups(groups); + new AbstractApi().getAll(new GroupSerializer(), (objects: Group[]) => { + setGroups(objects); setGroupsLoaded(true); }); } diff --git a/app/api/groups.tsx b/app/api/groups.tsx deleted file mode 100644 index 8c8081e..0000000 --- a/app/api/groups.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ - -import { Group } from '@/app/lib/definitions'; -import { getCsrfToken, getSlug } from '../lib/utils'; - -export function loadGroups(onLoad?: (groups: Group[]) => void) { - fetch(`/api/${getSlug()}/groups`) - .then((response) => response.json()) - .then((data) => { - onLoad && onLoad(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - color: record.color, - attendance: { - considered: record.considered, - invited: record.invited, - confirmed: record.confirmed, - tentative: record.tentative, - declined: record.declined, - total: record.total, - } - }); - })); - }, (error) => { - return []; - }); -} - -export function updateGroup(group: Group) { - return fetch(`/api/${getSlug()}/groups/${group.id}`, - { - method: 'PUT', - body: JSON.stringify({ group: { - name: group.name, - color: group.color, - icon: group.icon, - parent_id: group.parentId - } }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .catch((error) => console.error(error)); -} - -export function createGroup(group: Group, onCreate?: () => void) { - fetch(`/api/${getSlug()}/groups`, { - method: 'POST', - body: JSON.stringify({ - name: group.name, - color: group.color, - icon: group.icon, - parent_id: group.parentId - }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .then((response) => response.json()) - .then((data) => { - onCreate && onCreate(); - }) - .catch((error) => console.error(error)); -} - -export function destroyGroup(group: Group, onDestroy?: () => void) { - fetch(`/api/${getSlug()}/groups/${group.id}`, { - method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .then((response) => response.json()) - .then((data) => { - onDestroy && onDestroy(); - }) - .catch((error) => console.error(error)); -} \ No newline at end of file diff --git a/app/lib/definitions.ts b/app/lib/definitions.ts index 3f342f5..4968e24 100644 --- a/app/lib/definitions.ts +++ b/app/lib/definitions.ts @@ -21,25 +21,18 @@ export type TableArrangement = { discomfort?: number } -export type Group = { - id?: string; - name?: string; - guest_count?: number; - icon?: string; - children?: Group[]; - parentId?: string; - color?: string; - attendance?: AttendanceSummary -}; +// export type Group = { +// id?: string; +// name?: string; +// guest_count?: number; +// icon?: string; +// children?: Group[]; +// parentId?: string; +// color?: string; +// attendance?: AttendanceSummary +// }; + -export type AttendanceSummary = { - considered: number; - invited: number; - confirmed: number; - declined: number; - tentative: number; - total: number; -} export type guestsTable = { id: string; diff --git a/app/lib/group.tsx b/app/lib/group.tsx new file mode 100644 index 0000000..cc2adc5 --- /dev/null +++ b/app/lib/group.tsx @@ -0,0 +1,62 @@ +import { Entity } from "./definitions"; + +export type AttendanceSummary = { + considered: number; + invited: number; + confirmed: number; + declined: number; + tentative: number; + total: number; +} + +export class Group implements Entity { + id?: string; + name?: string; + guest_count?: number; + icon?: string; + children?: Group[]; + parentId?: string; + color?: string; + attendance?: AttendanceSummary + + constructor(id?: string, name?: string, guest_count?: number, icon?: string, children?: Group[], parentId?: string, color?: string, attendance?: AttendanceSummary) { + this.id = id; + this.name = name; + this.guest_count = guest_count; + this.icon = icon; + this.children = children; + this.parentId = parentId; + this.color = color; + this.attendance = attendance; + } +} + +export class GroupSerializer { + fromJson(data: any): Group { + return new Group( + data.id, + data.name, + data.guest_count, + data.icon, + data.children, + data.parent_id, + data.color, + data.attendance + ); + } + + toJson(group: Group): string { + return JSON.stringify({ + group: { + name: group.name, + color: group.color, + icon: group.icon, + parent_id: group.parentId + } + }); + } + + apiPath(): string { + return 'groups'; + } +} \ No newline at end of file diff --git a/app/lib/guest.tsx b/app/lib/guest.tsx index 1b2132f..6b7abcd 100644 --- a/app/lib/guest.tsx +++ b/app/lib/guest.tsx @@ -1,10 +1,10 @@ import { Serializable } from "../api/abstract-api"; +import { Entity } from "./definitions"; export const guestStatuses = ['considered', 'invited', 'confirmed', 'declined', 'tentative'] as const; export type GuestStatus = typeof guestStatuses[number]; - -export class Guest { +export class Guest implements Entity { id?: string; name?: string; group_name?: string; diff --git a/app/ui/components/group-form-dialog.tsx b/app/ui/components/group-form-dialog.tsx index 3ef1ec5..d652a13 100644 --- a/app/ui/components/group-form-dialog.tsx +++ b/app/ui/components/group-form-dialog.tsx @@ -2,8 +2,6 @@ 'use client'; -import { createGroup, updateGroup } from '@/app/api/groups'; -import { Group } from '@/app/lib/definitions'; import { classNames } from '@/app/ui/components/button'; import { Dialog } from 'primereact/dialog'; import { ColorPicker } from 'primereact/colorpicker'; @@ -11,6 +9,9 @@ import { Dropdown } from 'primereact/dropdown'; import { FloatLabel } from 'primereact/floatlabel'; import { InputText } from 'primereact/inputtext'; import { useState } from 'react'; +import { Group, GroupSerializer } from '@/app/lib/group'; +import { ApiError } from 'next/dist/server/api-utils'; +import { AbstractApi } from '@/app/api/abstract-api'; export default function GroupFormDialog({groups, onCreate, onHide, group, visible }: { groups: Group[], @@ -25,6 +26,9 @@ export default function GroupFormDialog({groups, onCreate, onHide, group, visibl const [color, setColor] = useState(group?.color || ''); const [parentId, setParentId] = useState(group?.parentId || ''); + const api = new AbstractApi(); + const serializer = new GroupSerializer(); + function resetForm() { setName(''); setIcon(''); @@ -43,12 +47,13 @@ export default function GroupFormDialog({groups, onCreate, onHide, group, visibl group.color = color; group.parentId = parentId; - updateGroup(group).then(() => { + api.update(serializer, group, () => { resetForm(); onCreate && onCreate(); }); } else { - group && createGroup({name, icon, color, parentId}, () => { + + api.create(serializer, new Group(undefined, name, undefined, icon, undefined, parentId, color), () => { resetForm(); onCreate && onCreate(); }); diff --git a/app/ui/components/guest-form-dialog.tsx b/app/ui/components/guest-form-dialog.tsx index fab5db9..9d8bec5 100644 --- a/app/ui/components/guest-form-dialog.tsx +++ b/app/ui/components/guest-form-dialog.tsx @@ -3,7 +3,7 @@ 'use client'; import { AbstractApi } from '@/app/api/abstract-api'; -import { Group } from '@/app/lib/definitions'; +import { Group } from '@/app/lib/group'; import { Guest, GuestSerializer, GuestStatus, guestStatuses } from '@/app/lib/guest'; import { capitalize } from '@/app/lib/utils'; import { classNames } from '@/app/ui/components/button'; diff --git a/app/ui/groups/table.tsx b/app/ui/groups/table.tsx index ca1240e..5e1f0df 100644 --- a/app/ui/groups/table.tsx +++ b/app/ui/groups/table.tsx @@ -2,10 +2,10 @@ 'use client'; -import { Group } from '@/app/lib/definitions'; +import { Group, GroupSerializer } from '@/app/lib/group'; import TableOfContents from '../components/table-of-contents'; import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; -import { destroyGroup } from '@/app/api/groups'; +import { AbstractApi } from '@/app/api/abstract-api'; export default function GroupsTable({ groups, onUpdate, onEdit }: { groups: Group[], @@ -13,6 +13,9 @@ export default function GroupsTable({ groups, onUpdate, onEdit }: { onEdit: (group: Group) => void, }) { + const api = new AbstractApi(); + const serializer = new GroupSerializer(); + return (
- { destroyGroup(group, () => onUpdate()) }} /> + { api.destroy(serializer, group, onUpdate) }} /> onEdit(group)} />