diff --git a/app/api/expenses.tsx b/app/api/expenses.tsx new file mode 100644 index 0000000..f73f6b7 --- /dev/null +++ b/app/api/expenses.tsx @@ -0,0 +1,40 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +import { Expense } from '@/app/lib/definitions'; +import { getCsrfToken } from '@/app/lib/utils'; + +export function loadExpenses(onLoad?: (expenses: Expense[]) => void) { + fetch("/api/expenses") + .then((response) => response.json()) + .then((data) => { + onLoad && onLoad(data.map((record: any) => { + return ({ + id: record.id, + name: record.name, + amount: record.amount, + pricingType: record.pricing_type + }); + })); + }, (error) => { + return []; + }); +} + +export function updateExpense(expense: Expense) { + fetch(`/api/expenses/${expense.id}`, + { + method: 'PUT', + body: JSON.stringify({ + expense: { + name: expense.name, + amount: expense.amount, + pricing_type: expense.pricingType, + } + }), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }) + .catch((error) => console.error(error)); +} \ No newline at end of file diff --git a/app/api/groups.tsx b/app/api/groups.tsx new file mode 100644 index 0000000..aa0e4db --- /dev/null +++ b/app/api/groups.tsx @@ -0,0 +1,27 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +import { Group } from '@/app/lib/definitions'; + +export function loadGroups(onLoad?: (groups: Group[]) => void) { + fetch("/api/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 []; + }); +} \ No newline at end of file diff --git a/app/api/guests.tsx b/app/api/guests.tsx new file mode 100644 index 0000000..37d0126 --- /dev/null +++ b/app/api/guests.tsx @@ -0,0 +1,50 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +import { Guest } from '@/app/lib/definitions'; +import { getCsrfToken } from '@/app/lib/utils'; + +export function loadGuests(onLoad?: (guests: Guest[]) => void) { + fetch("/api/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, + }); + })); + }, (error) => { + return []; + }); +}; + +export function updateGuest(guest: Guest) { + return fetch(`/api/guests/${guest.id}`, + { + method: 'PUT', + body: JSON.stringify({ guest: { name: guest.name, status: guest.status } }), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }) + .catch((error) => console.error(error)); +} + +export function createGuest(name: string, group_id: string, onCreate?: () => void) { + fetch("/api/guests", { + method: 'POST', + body: JSON.stringify({ name: name, group_id: group_id }), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }) + .then((response) => response.json()) + .then((data) => { + onCreate && onCreate(); + }) + .catch((error) => console.error(error)); +} \ No newline at end of file diff --git a/app/api/tableSimulations.tsx b/app/api/tableSimulations.tsx new file mode 100644 index 0000000..4e28688 --- /dev/null +++ b/app/api/tableSimulations.tsx @@ -0,0 +1,19 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +import { TableArrangement } from '@/app/lib/definitions'; + +export function loadTableSimulations(onLoad?: (tableSimulations: TableArrangement[]) => void) { + fetch('/api/tables_arrangements') + .then((response) => response.json()) + .then((data) => { + onLoad && onLoad(data.map((record: any) => { + return ({ + id: record.id, + name: record.name, + discomfort: record.discomfort, + }); + })); + }, (error) => { + return []; + }); +} \ No newline at end of file diff --git a/app/dashboard/guests/page.tsx b/app/dashboard/guests/page.tsx index 67bb05f..fe46b0e 100644 --- a/app/dashboard/guests/page.tsx +++ b/app/dashboard/guests/page.tsx @@ -3,7 +3,6 @@ 'use client'; import { Group, Guest } from '@/app/lib/definitions'; -import { getCsrfToken } from '@/app/lib/utils'; import CreationDialog from '@/app/ui/components/creation-dialog'; import GroupsTable from '@/app/ui/groups/table'; import AffinityGroupsTree from '@/app/ui/guests/affinity-groups-tree'; @@ -11,63 +10,22 @@ 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 { loadGuests } from '@/app/api/guests'; +import { loadGroups } from '@/app/api/groups'; export default function Page() { - function loadGroups() { - fetch("/api/groups") - .then((response) => response.json()) - .then((data) => { - setGroups(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, - } - }); - })); - setGroupsLoaded(true); - }, (error) => { - return []; - }); + function refreshGuests() { + loadGuests((guests) => { + setGuests(guests); + setGuestsLoaded(true); + }); } - function loadGuests() { - fetch("/api/guests.json") - .then((response) => response.json()) - .then((data) => { - setGuests(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - status: record.status, - group_name: record.group.name, - }); - })); - setGuestsLoaded(true); - }, (error) => { - return []; - }); - }; - - const updateGuestStatus = (id: string, status: string) => { - fetch("/api/guests/bulk_update.json", - { - method: 'POST', - body: JSON.stringify({ properties: { status: status }, guest_ids: [id] }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .then(() => loadGuests()) - .catch((error) => console.error(error)); + function refreshGroups() { + loadGroups((groups) => { + setGroups(groups); + setGroupsLoaded(true); + }); } const [groupsLoaded, setGroupsLoaded] = useState(false); @@ -76,8 +34,8 @@ export default function Page() { const [guestsLoaded, setGuestsLoaded] = useState(false); const [guests, setGuests] = useState>([]); - !groupsLoaded && loadGroups(); - !guestsLoaded && loadGuests(); + !groupsLoaded && refreshGroups(); + !guestsLoaded && refreshGuests(); return (
@@ -86,9 +44,9 @@ export default function Page() {
- ; + }> - +
diff --git a/app/lib/definitions.ts b/app/lib/definitions.ts index f1d3641..8e7e6ae 100644 --- a/app/lib/definitions.ts +++ b/app/lib/definitions.ts @@ -18,12 +18,13 @@ export type Customer = { image_url: string; }; +export type GuestStatus = 'considered' | 'invited' | 'confirmed' | 'declined' | 'tentative'; export type Guest = { id: string; name: string; group_name?: string; color?: string; - status?: 'considered' | 'invited' | 'confirmed' | 'declined' | 'tentative'; + status?: GuestStatus } export type Expense = { diff --git a/app/ui/arrangements/arrangements-table.tsx b/app/ui/arrangements/arrangements-table.tsx index 66e41d4..57dd67d 100644 --- a/app/ui/arrangements/arrangements-table.tsx +++ b/app/ui/arrangements/arrangements-table.tsx @@ -6,31 +6,24 @@ import React, { useState } from "react" import { TableArrangement } from '@/app/lib/definitions'; import { classNames } from "../components/button"; import TableOfContents from "../components/table-of-contents"; +import { loadTableSimulations } from "@/app/api/tableSimulations"; export default function ArrangementsTable ({onArrangementSelected}: {onArrangementSelected: (arrangementId: string) => void}) { const [arrangements, setArrangements] = useState>([]); + const [arrangementsLoaded, setArrangementsLoaded] = useState(false); - function loadArrangements() { - fetch("/api/tables_arrangements") - .then((response) => response.json()) - .then((data) => { - setArrangements(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - discomfort: record.discomfort - }); - })); - }, (error) => { - return []; - }); + function refreshSimulations() { + loadTableSimulations((arrangements) => { + setArrangements(arrangements); + setArrangementsLoaded(true); + }); } function arrangementClicked(e: React.MouseEvent) { onArrangementSelected(e.currentTarget.getAttribute('data-arrangement-id') || ''); } - arrangements.length === 0 && loadArrangements(); + !arrangementsLoaded && refreshSimulations(); return( response.json()) - .then((data) => { - console.log(data); - setVisible(false); - onCreate && onCreate(); - }) - .catch((error) => console.error(error)); + function submitGuest() { + name && group && createGuest(name, group, () => { + setVisible(false); + setName(''); + setGroup(null); + onCreate && onCreate(); + }); } return ( @@ -52,7 +43,7 @@ export default function CreationDialog({ groups, onCreate }: { groups: Group[], } /> - +
diff --git a/app/ui/expenses/table.tsx b/app/ui/expenses/table.tsx index 5161cfe..121990c 100644 --- a/app/ui/expenses/table.tsx +++ b/app/ui/expenses/table.tsx @@ -2,71 +2,43 @@ 'use client' -import React, { useState } from "react" +import { loadExpenses, updateExpense } from '@/app/api/expenses'; import { Expense } from '@/app/lib/definitions'; -import TableOfContents from "../components/table-of-contents"; +import { useState } from "react"; import InlineTextField from "../components/form/inlineTextField"; -import { getCsrfToken } from '@/app/lib/utils'; +import TableOfContents from "../components/table-of-contents"; export default function ExpensesTable() { - const [expenses, setExpenses] = useState>([]); + const [expenses, setExpenses] = useState>([]); + const [expensesLoaded, setExpensesLoaded] = useState(false); - const handleExpenseUpdate = (expense: Expense) => { - fetch(`/api/expenses/${expense.id}`, - { - method: 'PUT', - body: JSON.stringify({ - expense: { - name: expense.name, - amount: expense.amount, - pricing_type: expense.pricingType, - } - }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .catch((error) => console.error(error)); - } + function refreshExpenses() { + loadExpenses((expenses) => { + setExpenses(expenses); + setExpensesLoaded(true); + }); + } - function loadExpenses() { - fetch("/api/expenses") - .then((response) => response.json()) - .then((data) => { - setExpenses(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - amount: record.amount, - pricingType: record.pricing_type - }); - })); - }, (error) => { - return []; - }); - } + !expensesLoaded && refreshExpenses(); - expenses.length === 0 && loadExpenses(); - - return ( - ( - - - { expense.name = value; handleExpenseUpdate(expense) }} /> - - - { expense.amount = parseFloat(value); handleExpenseUpdate(expense) }} /> - - - {expense.pricingType} - - - )} - /> - ); + return ( + ( + + + { expense.name = value; updateExpense(expense) }} /> + + + { expense.amount = parseFloat(value); updateExpense(expense) }} /> + + + {expense.pricingType} + + + )} + /> + ); } \ No newline at end of file diff --git a/app/ui/guests/table.tsx b/app/ui/guests/table.tsx index 7b6d427..143147b 100644 --- a/app/ui/guests/table.tsx +++ b/app/ui/guests/table.tsx @@ -2,39 +2,19 @@ 'use client'; -import { Guest } from '@/app/lib/definitions'; -import { getCsrfToken } from '@/app/lib/utils'; +import { updateGuest } from '@/app/api/guests'; +import { Guest, GuestStatus } from '@/app/lib/definitions'; import { classNames } from '@/app/ui/components/button'; import clsx from 'clsx'; -import React from 'react'; import InlineTextField from '../components/form/inlineTextField'; import TableOfContents from '../components/table-of-contents'; -export default function guestsTable({ guests, updateGuestStatus }: { guests: Guest[], updateGuestStatus: (id: string, status: string) => void }) { - const handleInviteGuest = (e: React.MouseEvent) => handleGuestChange(e, 'invited'); - const handleConfirmGuest = (e: React.MouseEvent) => handleGuestChange(e, 'confirmed'); - const handleDeclineGuest = (e: React.MouseEvent) => handleGuestChange(e, 'declined'); - const handleTentativeGuest = (e: React.MouseEvent) => handleGuestChange(e, 'tentative'); - - const handleGuestUpdate = (guest: Guest) => { - fetch(`/api/guests/${guest.id}`, - { - method: 'PUT', - body: JSON.stringify({ guest: { name: guest.name } }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .catch((error) => console.error(error)); +export default function guestsTable({ guests, onUpdate }: { guests: Guest[], onUpdate: () => void }) { + const handleGuestChange = (guest: Guest, status: GuestStatus) => { + guest.status = status; + updateGuest(guest).then(() => onUpdate()); } - - const handleGuestChange = (e: React.MouseEvent, status: string) => { - updateGuestStatus(e.currentTarget.getAttribute('data-guest-id') || '', status); - } - - return ( ( - { guest.name = newName; handleGuestUpdate(guest) }} /> + { guest.name = newName; updateGuest(guest) }} /> {guest.group_name} @@ -65,14 +45,14 @@ export default function guestsTable({ guests, updateGuestStatus }: { guests: Gue - {guest.status === 'considered' && ()} {(guest.status === 'invited' || guest.status === 'tentative') && ( <> - - {guest.status != 'tentative' && } - + + {guest.status != 'tentative' && } + )} diff --git a/tests/guests.spec.ts b/tests/guests.spec.ts index 5fc76bc..734f623 100644 --- a/tests/guests.spec.ts +++ b/tests/guests.spec.ts @@ -1,7 +1,7 @@ import { test, expect, Page } from '@playwright/test' const mockGuestsAPI = ({ page }: { page: Page }) => { - page.route('*/**/api/guests.json', async route => { + page.route('*/**/api/guests', async route => { const json = [ { "id": "f4a09c28-40ea-4553-90a5-96935a59cac6",