Residents frontend page #82

Merged
BenjaminPalko merged 8 commits from 79-residents-frontend-page into master 2025-02-11 17:15:20 -05:00
5 changed files with 70 additions and 14 deletions
Showing only changes of commit 4f939c960e - Show all commits

View file

@ -24,7 +24,9 @@
"sms_button_submit": "Send Message", "sms_button_submit": "Send Message",
"residents_title": "Residents", "residents_title": "Residents",
"residents_button_new": "New Resident", "residents_button_new": "New Resident",
"residents_modal_title": "Create a Resident", "residents_table_edit": "Edit",
"residents_modal_title_new": "Create a Resident",
"residents_modal_title_edit": "Edit Resident",
"residents_modal_submit": "Submit", "residents_modal_submit": "Submit",
"residents_modal_label_name": "Name", "residents_modal_label_name": "Name",
"residents_modal_label_phone": "Phone Number", "residents_modal_label_phone": "Phone Number",

View file

@ -1,23 +1,34 @@
<script lang="ts" module>
export type ResidentItem = Pick<Resident, 'id' | 'name' | 'phoneNumber'>;
</script>
<script lang="ts"> <script lang="ts">
import type { Resident } from '@prisma/client'; import type { Resident } from '@prisma/client';
import clsx from 'clsx'; import clsx from 'clsx';
import { UserRoundPen } from 'lucide-svelte';
import type { SvelteHTMLElements } from 'svelte/elements'; import type { SvelteHTMLElements } from 'svelte/elements';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import Button from '../Actions/Button.svelte';
import { messages } from '$lib/i18n';
type Props = { type Props = {
items?: Pick<Resident, 'id' | 'name' | 'phoneNumber'>[]; items?: ResidentItem[];
onEdit?: (resident: ResidentItem) => void;
} & Omit<SvelteHTMLElements['table'], 'children'>; } & Omit<SvelteHTMLElements['table'], 'children'>;
let { items, class: className, ...props }: Props = $props(); let { items, onEdit, class: className, ...props }: Props = $props();
</script> </script>
<table {...props} class={twMerge('table', clsx(className))}> <table {...props} class={twMerge('table', clsx(className))}>
<!-- head --> <!-- head -->
<thead> <thead>
<tr class="bg-base-100"> <tr class="bg-base-100">
<th></th> <th>#</th>
<th>Name</th> <th>Name</th>
<th>Phone Number</th> <th>Phone Number</th>
{#if onEdit}
<th class="flex justify-end"><UserRoundPen class="mx-3" /></th>
{/if}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -28,6 +39,13 @@
<th>{index + 1}</th> <th>{index + 1}</th>
<td>{resident.name}</td> <td>{resident.name}</td>
<td>{resident.phoneNumber}</td> <td>{resident.phoneNumber}</td>
{#if onEdit}
<td class="text-end">
<Button size="sm" color="accent" onclick={() => onEdit(resident)}>
{messages.residents_table_edit()}
</Button>
</td>
{/if}
</tr> </tr>
{/each} {/each}
{/if} {/if}

View file

@ -1 +1,2 @@
export { default as ResidentTable } from './ResidentTable.svelte'; export { default as ResidentTable } from './ResidentTable.svelte';
export * from './ResidentTable.svelte';

View file

@ -32,6 +32,11 @@ export const actions = {
return fail(400, { error: 'message_missing' }); return fail(400, { error: 'message_missing' });
} }
const id = form.get('id');
if (id && typeof id !== 'string') {
return fail(400, { error: 'invalid_id' });
}
const name = form.get('name'); const name = form.get('name');
if (typeof name !== 'string') { if (typeof name !== 'string') {
return fail(400, { error: 'invalid_name' }); return fail(400, { error: 'invalid_name' });
@ -47,12 +52,19 @@ export const actions = {
return fail(400, { error: 'invalid_phone' }); return fail(400, { error: 'invalid_phone' });
} }
await prisma.resident.create({ await prisma.resident.upsert({
data: { where: {
id: id ?? '',
},
create: {
name: name, name: name,
phoneNumber: phone, phoneNumber: phone,
tenantId: event.locals.tenant.id, tenantId: event.locals.tenant.id,
}, },
update: {
name: name,
phoneNumber: phone,
},
}); });
}, },
}; };

View file

@ -2,7 +2,7 @@
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import { Button, Modal, ModalActions, ModalBody } from '$lib/components/Actions'; import { Button, Modal, ModalActions, ModalBody } from '$lib/components/Actions';
import { TextInput } from '$lib/components/DataInput'; import { TextInput } from '$lib/components/DataInput';
import { ResidentTable } from '$lib/components/Residents'; import { ResidentTable, type ResidentItem } from '$lib/components/Residents';
import { messages } from '$lib/i18n'; import { messages } from '$lib/i18n';
import { Phone, UserRound, UserRoundPlus } from 'lucide-svelte'; import { Phone, UserRound, UserRoundPlus } from 'lucide-svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@ -17,25 +17,42 @@
let residents = $derived(data.residents); let residents = $derived(data.residents);
let dialog = $state<HTMLDialogElement | undefined>(undefined); let dialog: HTMLDialogElement | undefined = $state(undefined);
let form: HTMLFormElement | undefined = $state(undefined);
let resident: ResidentItem | undefined = $state(undefined);
</script> </script>
<Modal backdrop bind:dialog> <Modal
backdrop
bind:dialog
onclose={() => {
resident = undefined;
form?.reset();
}}
>
<ModalBody> <ModalBody>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2 class="text-2xl">{messages.residents_modal_title()}</h2> <h2 class="text-2xl">
{resident
? messages.residents_modal_title_edit()
: messages.residents_modal_title_new()}
</h2>
<form method="dialog"> <form method="dialog">
<button class="btn btn-square btn-ghost btn-sm"></button> <button class="btn btn-square btn-ghost btn-sm"></button>
</form> </form>
</div> </div>
<form method="POST" use:enhance> <form method="POST" bind:this={form} use:enhance>
<TextInput bordered name="name"> {#if resident}
<input type="hidden" name="id" value={resident.id} />
{/if}
<TextInput bordered name="name" value={resident?.name}>
{#snippet label()} {#snippet label()}
<UserRound size="18" /> <UserRound size="18" />
{messages.residents_modal_label_name()} {messages.residents_modal_label_name()}
{/snippet} {/snippet}
</TextInput> </TextInput>
<TextInput bordered name="phoneNumber" type={'tel'}> <TextInput bordered name="phoneNumber" type={'tel'} value={resident?.phoneNumber}>
{#snippet label()} {#snippet label()}
<Phone size="18" /> <Phone size="18" />
{messages.residents_modal_label_phone()} {messages.residents_modal_label_phone()}
@ -61,5 +78,11 @@
> >
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<ResidentTable items={residents} /> <ResidentTable
items={residents}
onEdit={(r) => {
resident = r;
dialog?.showModal();
}}
/>
</div> </div>