diff --git a/app/[slug]/dashboard/guests/page.tsx b/app/[slug]/dashboard/guests/page.tsx index 0b7a88d..b98e688 100644 --- a/app/[slug]/dashboard/guests/page.tsx +++ b/app/[slug]/dashboard/guests/page.tsx @@ -3,11 +3,12 @@ 'use client'; import { loadGroups } from '@/app/api/groups'; -import { loadGuests } from '@/app/api/guests'; -import { Group, Guest } from '@/app/lib/definitions'; +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 GuestFormDialog from '@/app/ui/components/guest-form-dialog'; import GroupFormDialog from '@/app/ui/components/group-form-dialog'; +import GuestFormDialog from '@/app/ui/components/guest-form-dialog'; import GroupsTable from '@/app/ui/groups/table'; import SkeletonTable from '@/app/ui/guests/skeleton-row'; import GuestsTable from '@/app/ui/guests/table'; @@ -17,8 +18,8 @@ import { Suspense, useState } from 'react'; export default function Page() { function refreshGuests() { - loadGuests((guests) => { - setGuests(guests); + new AbstractApi().getAll(new GuestSerializer(), (objects: Guest[]) => { + setGuests(objects); setGuestsLoaded(true); }); } diff --git a/app/api/abstract-api.tsx b/app/api/abstract-api.tsx new file mode 100644 index 0000000..b43e72e --- /dev/null +++ b/app/api/abstract-api.tsx @@ -0,0 +1,65 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +import { Entity } from '@/app/lib/definitions'; +import { getCsrfToken, getSlug } from '@/app/lib/utils'; + +export interface Api { + getAll(serializable: Serializable ,callback: (objets: T[]) => void): void; + create(serializable: Serializable, object: T, callback: () => void): void; + update(serializable: Serializable, object: T, callback: () => void): void; + destroy(serializable: Serializable, object: T, callback: () => void): void; +} + +export interface Serializable { + fromJson(json: any): T; + toJson(object: T): string; + apiPath(): string; +} + +export class AbstractApi implements Api { + getAll(serializable: Serializable, callback: (objets: T[]) => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}`) + .then((response) => response.json()) + .then((data) => { + callback(data.map((record: any) => { + return serializable.fromJson(record); + })); + }, (error) => { + return []; + }); + } + + update(serializable: Serializable, object: T, callback: () => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}/${object.id}`, { + method: 'PUT', + body: serializable.toJson(object), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(callback) + .catch((error) => console.error(error)); + } + + create(serializable: Serializable, object: T, callback: () => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}`, { + method: 'POST', + body: serializable.toJson(object), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(callback) + .catch((error) => console.error(error)); + } + + destroy(serializable: Serializable, object: T, callback: () => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}/${object.id}`, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(callback) + .catch((error) => console.error(error)); + } +} diff --git a/app/api/guests.tsx b/app/api/guests.tsx deleted file mode 100644 index ce4f232..0000000 --- a/app/api/guests.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ - -import { Guest } from '@/app/lib/definitions'; -import { getCsrfToken, getSlug } from '@/app/lib/utils'; - -export function loadGuests(onLoad?: (guests: Guest[]) => void) { - fetch(`/api/${getSlug()}/guests`) - .then((response) => response.json()) - .then((data) => { - onLoad && onLoad(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - status: record.status, - group_name: record.group.name, - groupId: record.group.id, - }); - })); - }, (error) => { - return []; - }); -}; - -export function updateGuest(guest: Guest) { - return fetch(`/api/${getSlug()}/guests/${guest.id}`, - { - method: 'PUT', - body: JSON.stringify({ guest: { name: guest.name, status: guest.status, group_id: guest.groupId } }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .catch((error) => console.error(error)); -} - -export function createGuest(guest: Guest, onCreate?: () => void) { - fetch(`/api/${getSlug()}/guests`, { - method: 'POST', - body: JSON.stringify({ name: guest.name, group_id: guest.groupId, status: guest.status }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .then((response) => response.json()) - .then((data) => { - onCreate && onCreate(); - }) - .catch((error) => console.error(error)); -} - -export function destroyGuest(guest: Guest, onDestroy?: () => void) { - fetch(`/api/${getSlug()}/guests/${guest.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 464cb83..3f342f5 100644 --- a/app/lib/definitions.ts +++ b/app/lib/definitions.ts @@ -1,19 +1,9 @@ /* Copyright (C) 2024 Manuel Bustillo*/ -// This file contains type definitions for your data. -// It describes the shape of the data, and what data type each property should accept. -// For simplicity of teaching, we're manually defining these types. -// However, these types are generated automatically if you're using an ORM such as Prisma. +import { Guest } from "./guest"; -export const guestStatuses = ['considered', 'invited', 'confirmed', 'declined', 'tentative'] as const; -export type GuestStatus = typeof guestStatuses[number]; -export type Guest = { +export interface Entity { id?: string; - name?: string; - group_name?: string; - groupId?: string; - color?: string; - status?: GuestStatus } export type Expense = { @@ -73,5 +63,5 @@ export type Captcha = { } export type StructuredErrors = { - [key: string]: string[]|string; + [key: string]: string[] | string; }; \ No newline at end of file diff --git a/app/lib/guest.tsx b/app/lib/guest.tsx new file mode 100644 index 0000000..1b2132f --- /dev/null +++ b/app/lib/guest.tsx @@ -0,0 +1,38 @@ +import { Serializable } from "../api/abstract-api"; + +export const guestStatuses = ['considered', 'invited', 'confirmed', 'declined', 'tentative'] as const; +export type GuestStatus = typeof guestStatuses[number]; + + +export class Guest { + id?: string; + name?: string; + group_name?: string; + groupId?: string; + color?: string; + status?: GuestStatus; + + constructor(id?: string, name?: string, group_name?: string, groupId?: string, color?: string, status?: GuestStatus) { + this.id = id; + this.name = name; + this.group_name = group_name; + this.groupId = groupId; + this.color = color; + this.status = status; + } +} + +export class GuestSerializer implements Serializable { + fromJson(data: any): Guest { + return new Guest(data.id, data.name, data.group_name, data.group_id, data.color, data.status); + } + + toJson(guest: Guest): string { + return JSON.stringify({ guest: { name: guest.name, status: guest.status, group_id: guest.groupId } }); + } + + apiPath(): string { + return 'guests'; + } +} + diff --git a/app/ui/components/guest-form-dialog.tsx b/app/ui/components/guest-form-dialog.tsx index bf94230..fab5db9 100644 --- a/app/ui/components/guest-form-dialog.tsx +++ b/app/ui/components/guest-form-dialog.tsx @@ -2,8 +2,9 @@ 'use client'; -import { createGuest, updateGuest } from '@/app/api/guests'; -import { Group, Guest, GuestStatus, guestStatuses } from '@/app/lib/definitions'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { Group } from '@/app/lib/definitions'; +import { Guest, GuestSerializer, GuestStatus, guestStatuses } from '@/app/lib/guest'; import { capitalize } from '@/app/lib/utils'; import { classNames } from '@/app/ui/components/button'; import { Dialog } from 'primereact/dialog'; @@ -24,6 +25,9 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib const [group, setGroup] = useState(guest?.groupId || null); const [status, setStatus] = useState(guest?.status || null); + const api = new AbstractApi(); + const serializer = new GuestSerializer(); + function resetForm() { setName(''); setGroup(null); @@ -37,14 +41,15 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib if (guest?.id !== undefined) { guest.name = name; - guest.groupId = group; + guest.groupId = group; guest.status = status; - updateGuest(guest).then(() => { + + api.update(serializer, guest, () => { resetForm(); onCreate && onCreate(); }); } else { - guest && createGuest({name: name, groupId: group, status: status}, () => { + api.create(serializer, new Guest(undefined, name, undefined, group, undefined, status), ()=> { resetForm(); onCreate && onCreate(); }); diff --git a/app/ui/guests/table.tsx b/app/ui/guests/table.tsx index 304c357..4f3694b 100644 --- a/app/ui/guests/table.tsx +++ b/app/ui/guests/table.tsx @@ -2,9 +2,8 @@ 'use client'; -import { destroyGuest, updateGuest } from '@/app/api/guests'; -import { Guest, GuestStatus } from '@/app/lib/definitions'; -import { classNames } from '@/app/ui/components/button'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { Guest , GuestSerializer} from '@/app/lib/guest'; import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; import clsx from 'clsx'; import TableOfContents from '../components/table-of-contents'; @@ -14,6 +13,10 @@ export default function guestsTable({ guests, onUpdate, onEdit }: { onUpdate: () => void, onEdit: (guest: Guest) => void }) { + + const api = new AbstractApi(); + const serializer = new GuestSerializer(); + return (
- { destroyGuest(guest, () => onUpdate()) }} /> + { api.destroy(serializer, guest, onUpdate)}} /> onEdit(guest)} />