From 147deacaca91bc509f0f203040ac80cf123c8b09 Mon Sep 17 00:00:00 2001 From: Ian Day Date: Thu, 1 Jan 2026 18:33:14 -0500 Subject: [PATCH 1/5] fix(GuestList): :art: minor UI changes --- frontend/src/lib/components/BackToTop.svelte | 2 +- frontend/src/lib/components/Faq.svelte | 8 ++++---- frontend/src/lib/components/rsvp/RsvpAccept.svelte | 4 ++-- frontend/src/lib/components/rsvp/RsvpComplete.svelte | 5 +---- frontend/src/lib/components/rsvp/RsvpLanding.svelte | 7 ++++--- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/components/BackToTop.svelte b/frontend/src/lib/components/BackToTop.svelte index 9bf599b..2e71d47 100644 --- a/frontend/src/lib/components/BackToTop.svelte +++ b/frontend/src/lib/components/BackToTop.svelte @@ -25,6 +25,6 @@ data-show={$show} on:click={toTop} data-tip="Back to top" - class="btn tooltip btn-circle translate-y-4 scale-90 opacity-0 transition-all duration-300 data-[show=true]:translate-y-0 data-[show=true]:scale-100 data-[show=true]:opacity-100"> + class="btn bg-accent tooltip tooltip-accent btn-circle translate-y-4 scale-90 opacity-0 transition-all duration-300 data-[show=true]:translate-y-0 data-[show=true]:scale-100 data-[show=true]:opacity-100"> diff --git a/frontend/src/lib/components/Faq.svelte b/frontend/src/lib/components/Faq.svelte index 221bb01..0fcc654 100644 --- a/frontend/src/lib/components/Faq.svelte +++ b/frontend/src/lib/components/Faq.svelte @@ -33,17 +33,17 @@
-
+

{cat.categoryName}

-
+
{#each cat.questions as question (question.id)}
- + class="collapse collapse-arrow bg-secondary text-secondary-content border border-base-300 shadow-md hover:shadow-lg transition-shadow"> +
diff --git a/frontend/src/lib/components/rsvp/RsvpAccept.svelte b/frontend/src/lib/components/rsvp/RsvpAccept.svelte index c7bb20e..6247c0f 100644 --- a/frontend/src/lib/components/rsvp/RsvpAccept.svelte +++ b/frontend/src/lib/components/rsvp/RsvpAccept.svelte @@ -252,13 +252,13 @@
{#if preview}
Back
-
+
Submit RSVP
{:else} Back - diff --git a/frontend/src/lib/components/layouts/Breadcrumbs.svelte b/frontend/src/lib/components/layouts/Breadcrumbs.svelte new file mode 100644 index 0000000..84066f6 --- /dev/null +++ b/frontend/src/lib/components/layouts/Breadcrumbs.svelte @@ -0,0 +1,28 @@ + + + diff --git a/frontend/src/lib/components/layouts/ProtectedPageShell.svelte b/frontend/src/lib/components/layouts/ProtectedPageShell.svelte index 40cf42b..e025330 100644 --- a/frontend/src/lib/components/layouts/ProtectedPageShell.svelte +++ b/frontend/src/lib/components/layouts/ProtectedPageShell.svelte @@ -1,11 +1,26 @@
+
diff --git a/frontend/src/lib/components/object/ObjectChildItems.svelte b/frontend/src/lib/components/object/ObjectChildItems.svelte new file mode 100644 index 0000000..f7342b2 --- /dev/null +++ b/frontend/src/lib/components/object/ObjectChildItems.svelte @@ -0,0 +1,31 @@ + + +
+
+

+ + + + {title} +

+ {@render children()} +
+
diff --git a/frontend/src/lib/components/object/ObjectDetail.svelte b/frontend/src/lib/components/object/ObjectDetail.svelte new file mode 100644 index 0000000..0f19cfb --- /dev/null +++ b/frontend/src/lib/components/object/ObjectDetail.svelte @@ -0,0 +1,86 @@ + + + +
+
+

{title}

+ +
+
+ Edit + +
+
+
+
+
+
Item Details
+ {@render mainSnippet()} + + {#if mainActionsSnippet} +
{@render mainActionsSnippet()}
+ {/if} +
+
+
+
+
Record Information
+
+
+
Created By
+
{object.createdByName}
+
+
+
Created At
+
{object.createdAt}
+
+
+
Last Updated By
+
{object.updatedByName}
+
+
+
Last Updated At
+
{object.updatedAt}
+
+
+
+
+ + {#if extraCardsSnippet} + {@render extraCardsSnippet()} + {/if} +
+
diff --git a/frontend/src/lib/components/object/ObjectStatus.svelte b/frontend/src/lib/components/object/ObjectStatus.svelte new file mode 100644 index 0000000..0a266bd --- /dev/null +++ b/frontend/src/lib/components/object/ObjectStatus.svelte @@ -0,0 +1,14 @@ + + +{#if status} +
✓ {text}
+{:else} +
⏳ {text}
+{/if} diff --git a/frontend/src/lib/server/api-client.ts b/frontend/src/lib/server/api-client.ts index b357982..6217d6d 100644 --- a/frontend/src/lib/server/api-client.ts +++ b/frontend/src/lib/server/api-client.ts @@ -2,7 +2,7 @@ * Server-side API client using the OpenAPI generated client * Configured with service token authentication for backend-to-backend communication */ -import { Configuration, GuestlistApi, CoreApi } from "../../../api-client"; +import { Configuration, GuestlistApi, CoreApi, DeadlinesApi } from "../../../api-client"; import type { Middleware } from "../../../api-client/runtime"; const API_BASE_PATH = process.env.BACKEND_API_URL; @@ -79,6 +79,7 @@ export function createApiClient() { return { guestlist: new GuestlistApi(config), core: new CoreApi(config), + deadlines: new DeadlinesApi(config), }; } diff --git a/frontend/src/lib/styles/core/components.css b/frontend/src/lib/styles/core/components.css index e6fb1b1..acdbabe 100644 --- a/frontend/src/lib/styles/core/components.css +++ b/frontend/src/lib/styles/core/components.css @@ -152,3 +152,100 @@ input:-webkit-autofill:active { .edit-card-field-radio { @apply radio radio-primary; } + +.list-card { + @apply card bg-primary text-primary-content shadow-md transition-shadow duration-300 hover:shadow-lg; +} + +.list-card-title { + @apply card-title text-accent text-lg lg:text-xl; +} + +.list-card-body { + @apply card-body text-primary-content px-6 py-4; +} + +.list-card-item { + @apply text-primary-content/90; +} + +.list-card-actions { + @apply card-actions border-base-300 mt-4 justify-end border-t pt-4; +} + +/* start detail object view */ +.detail-card { + @apply card bg-base-300 text-primary-content shadow-xl transition-shadow duration-300 hover:shadow-2xl; +} + +.detail-card-title { + @apply card-title text-accent mb-4 text-lg lg:text-xl; +} + +.detail-card-body { + @apply card-body px-6 py-4; +} + +.detail-card-field-name { + @apply text-primary-content/60 font-semibold; +} + +.detail-card-field-value { + @apply text-primary-content font-medium; +} + +.detail-card-field-value-secondary { + @apply text-primary-content/50 font-medium; +} + +.detail-card-actions { + @apply card-actions mt-4 justify-center space-x-4; +} + +.detail-action-card { + @apply card bg-base-200 text-base-content mt-6 shadow-xl transition-shadow duration-300 hover:shadow-2xl; +} + +.detail-action-card-title { + @apply card-title text-base-content mb-4 text-lg lg:text-xl; +} + +.detail-action-card-body { + @apply card-body px-6 py-4; +} + +/* end detail object view */ +/* start table listing items from associated models */ +.associated-card { + @apply card bg-neutral text-base-content shadow-xl transition-shadow duration-300 hover:shadow-2xl; +} + +.associated-card-title { + @apply card-title text-base-content text-lg lg:text-xl; +} + +.associated-card-body { + @apply card-body px-6 py-4; +} + +.associated-table { + @apply bg-base-300 text-secondary-content table w-full; +} + +.associated-table-header { + @apply text-primary; +} + +.associated-table-checkbox { + @apply checkbox border-neutral bg-base-100 checked:bg-base-100 checked:text-accent-content; +} + +.associated-table-toggle { + @apply toggle border-neutral bg-base-100 checked:text-accent-content; +} + +.associated-table-footer { + @apply font-bold text-blue-500; +} + +/* end table listing items from associated models */ diff --git a/frontend/src/lib/styles/daisyui.css b/frontend/src/lib/styles/daisyui.css index 91f64e3..537d9fb 100644 --- a/frontend/src/lib/styles/daisyui.css +++ b/frontend/src/lib/styles/daisyui.css @@ -17,11 +17,11 @@ --color-neutral-content: #1a1f17; --color-info: #6b9dc9; --color-info-content: #f0f5fa; - --color-success: #819d76; - --color-success-content: #1a1f17; + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); --color-warning: #e0960b; --color-warning-content: #1a0f02; - --color-error: #a84856; + --color-error: #ed4337; --color-error-content: #fef3f5; --color-base-100: #303a2b; @@ -38,9 +38,33 @@ --border: 1px; --card-p: 20px; --card-fs: var(--text-base); + --depth: 1; + --noise: 1; + --ck-spacking-standard: 3em; --depth: 0; --noise: 0; + /* Component styling */ + --rounded-box: 0rem; + --rounded-btn: 0rem; + --rounded-badge: 0rem; + --animation-btn: 0.25s; + --animation-input: 0.2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0rem; + /* curves are bad, i guess */ + --radius-box: 0rem; + --radius-card: 0rem; + --radius-input: 0rem; + --radius-field: 0rem; + --radius-select: 0rem; + --radius-toggle: 0rem; + --radius-badge: 0rem; + --radius-dropdown: 0rem; + --radius-modal: 0rem; + --radius-alert: 0rem; } @plugin "daisyui/theme" { diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte new file mode 100644 index 0000000..2bc850a --- /dev/null +++ b/frontend/src/routes/settings/+page.svelte @@ -0,0 +1,126 @@ + + + +
+ +
+

Wedding Settings

+

+ Manage all aspects of your wedding website from this central hub. Configure settings, organize content, + and track your planning progress. +

+
+ + + + + +
+
+

Quick Tips

+
+
+ +
+

Start with Configuration

+

Set up your wedding date and RSVP settings first

+
+
+
+ +
+

Preview Before Publishing

+

Use preview pages to see guest experience

+
+
+
+ +
+

Stay Organized

+

Use deadlines to track all planning tasks

+
+
+
+
+
+
+
diff --git a/frontend/src/routes/settings/config/+page.svelte b/frontend/src/routes/settings/config/+page.svelte index 5a259e5..90ead8f 100644 --- a/frontend/src/routes/settings/config/+page.svelte +++ b/frontend/src/routes/settings/config/+page.svelte @@ -37,13 +37,15 @@ ], }, }; + + const relativeCrumbs = [{ title: "Settings" }];
-
-

Wedding Configuration

+

Wedding Settings

Edit Settings
diff --git a/frontend/src/routes/settings/config/edit/+page.svelte b/frontend/src/routes/settings/config/edit/+page.svelte index a8f81c7..6d0f19e 100644 --- a/frontend/src/routes/settings/config/edit/+page.svelte +++ b/frontend/src/routes/settings/config/edit/+page.svelte @@ -45,9 +45,11 @@ { name: "showFaq", type: "checkbox" }, { name: "weddingDate", type: "date" }, ]; + + const relativeCrumbs = [{ title: "Configuration", href: "/settings/config" }, { title: "Edit" }]; - +

Edit Wedding Configuration

diff --git a/frontend/src/routes/settings/deadline/+page.server.ts b/frontend/src/routes/settings/deadline/+page.server.ts new file mode 100644 index 0000000..7697748 --- /dev/null +++ b/frontend/src/routes/settings/deadline/+page.server.ts @@ -0,0 +1,10 @@ +import { api } from "$lib/server/api-client"; +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async () => { + const deadlineLists = await api.deadlines.deadlineApiListDeadlineLists({}); + + return { + deadlineLists: deadlineLists.items || [], + }; +}; diff --git a/frontend/src/routes/settings/deadline/+page.svelte b/frontend/src/routes/settings/deadline/+page.svelte new file mode 100644 index 0000000..03d1239 --- /dev/null +++ b/frontend/src/routes/settings/deadline/+page.svelte @@ -0,0 +1,73 @@ + + + + +
+
+

Deadline Summary

+

Manage your wedding planning deadlines and tasks

+
+ + + New List + +
+ + {#if data.deadlineLists.length === 0} + +
+
+ +

No deadline lists yet

+

+ Create your first deadline list to start organizing your wedding planning tasks and staying on + track. +

+ + + Create First List + +
+
+ {:else} + +
+ {#each data.deadlineLists as list (list.id)} +
+
+ +
+
✓ {list.completedCount}
+
⏳ {list.pendingCount}
+
+
+
+ Progress + {list.completionPercentage}% +
+ +
+ +
+ + Last updated {new Date(list.updatedAt).toLocaleDateString()} + +
+
+
+ {/each} +
+ {/if} +
diff --git a/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts b/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts new file mode 100644 index 0000000..0a507ea --- /dev/null +++ b/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts @@ -0,0 +1,18 @@ +import { api } from "$lib/server/api-client"; +import { error } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ params }) => { + try { + const deadline = await api.deadlines.deadlineApiGetDeadline({ + deadlineId: params.id, + }); + + return { + deadline, + }; + } catch (err) { + console.error("Error loading deadline:", err); + throw error(404, "Deadline not found"); + } +}; diff --git a/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte b/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte new file mode 100644 index 0000000..d148d38 --- /dev/null +++ b/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte @@ -0,0 +1,108 @@ + + + + {#snippet mainSnippet()} +
+ {#if data.deadline.description} +
+
Description
+
{data.deadline.description}
+
+ {/if} + +
+
+
Due Date
+
+ {formatDate(data.deadline.dueDate)} + {#if data.deadline.overdue && !data.deadline.completed} + Overdue + {/if} +
+
+ +
+
Status
+
+ {#if data.deadline.completed} + + + Completed + + {:else} + + + Pending + + {/if} +
+
+
+ + {#if data.deadline.assignedToName} +
+
Assigned To
+
+ + {data.deadline.assignedToName} +
+
+ {/if} + + {#if data.deadline.completed && data.deadline.completedNote} +
+
Completion Note
+
{data.deadline.completedNote}
+
+ {/if} +
+ {/snippet} + + {#snippet mainActionsSnippet()} + + + Edit + + {/snippet} +
diff --git a/frontend/src/routes/settings/deadline/deadline/[id]/edit/+page.server.ts b/frontend/src/routes/settings/deadline/deadline/[id]/edit/+page.server.ts new file mode 100644 index 0000000..3be7a6a --- /dev/null +++ b/frontend/src/routes/settings/deadline/deadline/[id]/edit/+page.server.ts @@ -0,0 +1,58 @@ +import { api } from "$lib/server/api-client"; +import { fail, redirect } from "@sveltejs/kit"; +import { error } from "@sveltejs/kit"; +import type { Actions, PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ params }) => { + const deadlineId = params.id; + + try { + const deadline = await api.deadlines.deadlineApiGetDeadline({ deadlineId }); + + return { + deadline, + }; + } catch (err) { + console.error("Failed to load deadline:", err); + throw error(404, "Deadline not found"); + } +}; + +export const actions = { + default: async ({ request, params }) => { + const deadlineId = params.id; + const formData = await request.formData(); + const name = formData.get("name"); + const description = formData.get("description"); + const dueDate = formData.get("dueDate"); + const completed = formData.get("completed") === "on"; + const completedNote = formData.get("completedNote"); + + if (!name || typeof name !== "string") { + return fail(400, { error: "Deadline name is required" }); + } + + let updatedDeadline; + try { + updatedDeadline = await api.deadlines.deadlineApiUpdateDeadline({ + deadlineId, + deadlineUpdateSchema: { + name, + description: description && typeof description === "string" ? description : undefined, + dueDate: dueDate && typeof dueDate === "string" ? new Date(dueDate) : undefined, + completed, + completedNote: completedNote && typeof completedNote === "string" ? completedNote : undefined, + }, + }); + } catch (err) { + console.error("Failed to update deadline:", err); + return fail(500, { error: "Failed to update deadline" }); + } + + const redirectPath = updatedDeadline.deadlineListId + ? `/settings/deadline/list/${updatedDeadline.deadlineListId}` + : "/settings/deadline"; + + throw redirect(303, redirectPath); + }, +} satisfies Actions; diff --git a/frontend/src/routes/settings/deadline/deadline/[id]/edit/+page.svelte b/frontend/src/routes/settings/deadline/deadline/[id]/edit/+page.svelte new file mode 100644 index 0000000..33e7d6a --- /dev/null +++ b/frontend/src/routes/settings/deadline/deadline/[id]/edit/+page.svelte @@ -0,0 +1,142 @@ + + + +
+
+

Edit Deadline

+ Cancel +
+ + {#if form?.error} +
+ + + + {form.error} +
+ {/if} + +
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + {#if data.deadline.overdue && !data.deadline.completed} + This deadline is overdue + {/if} +
+ +
+ + + {#if data.deadline.completedAt} + + Completed on {new Date(data.deadline.completedAt).toLocaleDateString()} + + {/if} +
+ +
+ + +
+
+
+
+ +
+ Cancel + +
+
+
+
diff --git a/frontend/src/routes/settings/deadline/list/[id]/+page.server.ts b/frontend/src/routes/settings/deadline/list/[id]/+page.server.ts new file mode 100644 index 0000000..3c80e64 --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/[id]/+page.server.ts @@ -0,0 +1,22 @@ +import { api } from "$lib/server/api-client"; +import { error } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ params }) => { + const listId = params.id; + + try { + const [deadlineList, deadlines] = await Promise.all([ + api.deadlines.deadlineApiGetDeadlineList({ listId }), + api.deadlines.deadlineApiListDeadlines({ deadlineListId: listId }), + ]); + + return { + deadlineList, + deadlines: deadlines.items || [], + }; + } catch (err) { + console.error("Failed to load deadline list:", err); + throw error(404, "Deadline list not found"); + } +}; diff --git a/frontend/src/routes/settings/deadline/list/[id]/+page.svelte b/frontend/src/routes/settings/deadline/list/[id]/+page.svelte new file mode 100644 index 0000000..c66e6a6 --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/[id]/+page.svelte @@ -0,0 +1,106 @@ + + + + {#snippet mainSnippet()} +
+
+
Total
+
{data.deadlineList.count}
+
+
+
Completed
+
{data.deadlineList.completedCount}
+
+
+
Pending
+
{data.deadlineList.pendingCount}
+
+
+ {/snippet} + {#snippet mainActionsSnippet()} + + {/snippet} + {#snippet extraCardsSnippet()} + + {#if data.deadlines.length === 0} +
+
+ +

No deadlines yet

+

+ Add deadlines to this list to help you stay on track with your wedding planning tasks. +

+ + + Add First Deadline + +
+
+ {:else} +
+ {#each data.deadlines as deadline (deadline.id)} +
+
+ + {#if deadline.description} +
{deadline.description}
+ {/if} +
+ {#if deadline.dueDate} + Due: {new Date(deadline.dueDate).toLocaleDateString()} + {:else} + No due date set + {/if} + {#if deadline.assignedToName} + Assigned to: {deadline.assignedToName} + {:else} + Unassigned + {/if} +
+
+ {#if deadline.completed} +
Completed
+ {:else} +
Pending
+ {/if} +
+
+
+ {/each} +
+ {/if} +
+ {/snippet} +
diff --git a/frontend/src/routes/settings/deadline/list/[id]/deadline/new/+page.server.ts b/frontend/src/routes/settings/deadline/list/[id]/deadline/new/+page.server.ts new file mode 100644 index 0000000..dbd37ec --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/[id]/deadline/new/+page.server.ts @@ -0,0 +1,42 @@ +import { api } from "$lib/server/api-client"; +import { fail, redirect } from "@sveltejs/kit"; +import type { Actions, PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ params }) => { + return { + listId: params.id, + listName: (await api.deadlines.deadlineApiGetDeadlineList({ listId: params.id })).name, + }; +}; + +export const actions = { + default: async ({ request, params }) => { + const listId = params.id; + const formData = await request.formData(); + const name = formData.get("name"); + const description = formData.get("description"); + const dueDate = formData.get("dueDate"); + const completed = formData.get("completed") === "on"; + + if (!name || typeof name !== "string") { + return fail(400, { error: "Deadline name is required" }); + } + + try { + await api.deadlines.deadlineApiCreateDeadline({ + deadlineCreateSchema: { + name, + description: description && typeof description === "string" ? description : undefined, + deadlineListId: listId, + dueDate: dueDate && typeof dueDate === "string" ? new Date(dueDate) : undefined, + completed, + }, + }); + } catch (err) { + console.error("Failed to create deadline:", err); + return fail(500, { error: "Failed to create deadline" }); + } + + throw redirect(303, `/settings/deadline/list/${listId}`); + }, +} satisfies Actions; diff --git a/frontend/src/routes/settings/deadline/list/[id]/deadline/new/+page.svelte b/frontend/src/routes/settings/deadline/list/[id]/deadline/new/+page.svelte new file mode 100644 index 0000000..0e88856 --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/[id]/deadline/new/+page.svelte @@ -0,0 +1,95 @@ + + + +
+
+

Add Deadline

+ Cancel +
+ + {#if form?.error} +
+ + + + {form.error} +
+ {/if} + +
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ +
+ Cancel + +
+
+
+
diff --git a/frontend/src/routes/settings/deadline/list/[id]/edit/+page.server.ts b/frontend/src/routes/settings/deadline/list/[id]/edit/+page.server.ts new file mode 100644 index 0000000..9f1e09b --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/[id]/edit/+page.server.ts @@ -0,0 +1,43 @@ +import { api } from "$lib/server/api-client"; +import { fail, redirect } from "@sveltejs/kit"; +import { error } from "@sveltejs/kit"; +import type { Actions, PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ params }) => { + const listId = params.id; + + try { + const deadlineList = await api.deadlines.deadlineApiGetDeadlineList({ listId }); + + return { + deadlineList, + }; + } catch (err) { + console.error("Failed to load deadline list:", err); + throw error(404, "Deadline list not found"); + } +}; + +export const actions = { + default: async ({ request, params }) => { + const listId = params.id; + const formData = await request.formData(); + const name = formData.get("name"); + + if (!name || typeof name !== "string") { + return fail(400, { error: "List name is required" }); + } + + try { + await api.deadlines.deadlineApiUpdateDeadlineList({ + listId, + deadlineListUpdateSchema: { name }, + }); + } catch (err) { + console.error("Failed to update deadline list:", err); + return fail(500, { error: "Failed to update deadline list" }); + } + + throw redirect(303, `/settings/deadline/list/${listId}`); + }, +} satisfies Actions; diff --git a/frontend/src/routes/settings/deadline/list/[id]/edit/+page.svelte b/frontend/src/routes/settings/deadline/list/[id]/edit/+page.svelte new file mode 100644 index 0000000..01138fd --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/[id]/edit/+page.svelte @@ -0,0 +1,65 @@ + + + +
+
+

Edit Deadline List

+ Cancel +
+ + {#if form?.error} +
+ + + + {form.error} +
+ {/if} + +
+
+
+

Update the name of your deadline list

+
+
+ + +
+
+
+
+ +
+ Cancel + +
+
+
+
diff --git a/frontend/src/routes/settings/deadline/list/new/+page.server.ts b/frontend/src/routes/settings/deadline/list/new/+page.server.ts new file mode 100644 index 0000000..07e379a --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/new/+page.server.ts @@ -0,0 +1,30 @@ +import { api } from "$lib/server/api-client"; +import { fail, redirect } from "@sveltejs/kit"; +import type { Actions, PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async () => { + return {}; +}; + +export const actions = { + default: async ({ request }) => { + const formData = await request.formData(); + const name = formData.get("name"); + + if (!name || typeof name !== "string") { + return fail(400, { error: "List name is required" }); + } + + let newList; + try { + newList = await api.deadlines.deadlineApiCreateDeadlineList({ + deadlineListCreateSchema: { name }, + }); + } catch (err) { + console.error("Failed to create deadline list:", err); + return fail(500, { error: "Failed to create deadline list" }); + } + + throw redirect(303, `/settings/deadline/list/${newList.id}`); + }, +} satisfies Actions; diff --git a/frontend/src/routes/settings/deadline/list/new/+page.svelte b/frontend/src/routes/settings/deadline/list/new/+page.svelte new file mode 100644 index 0000000..d1175d2 --- /dev/null +++ b/frontend/src/routes/settings/deadline/list/new/+page.svelte @@ -0,0 +1,70 @@ + + + +
+
+

Create Deadline List

+ Cancel +
+ + {#if form?.error} +
+ + + + {form.error} +
+ {/if} + +
+
+
+

+ Create a new list to organize your wedding planning deadlines +

+
+
+ + + + Choose a descriptive name for your deadline list + +
+
+
+
+ +
+ Cancel + +
+
+
+
diff --git a/frontend/src/routes/settings/faq/+page.svelte b/frontend/src/routes/settings/faq/+page.svelte index 00accb7..2f852f4 100644 --- a/frontend/src/routes/settings/faq/+page.svelte +++ b/frontend/src/routes/settings/faq/+page.svelte @@ -70,9 +70,10 @@ const totalQuestions = data.questions?.length || 0; const totalTips = data.tips?.length || 0; + const relativeCrumbs = [{ title: "FAQ" }]; - +
diff --git a/frontend/src/routes/settings/faq/question/[id]/edit/+page.svelte b/frontend/src/routes/settings/faq/question/[id]/edit/+page.svelte index 8d35a3c..26b9092 100644 --- a/frontend/src/routes/settings/faq/question/[id]/edit/+page.svelte +++ b/frontend/src/routes/settings/faq/question/[id]/edit/+page.svelte @@ -20,9 +20,11 @@ editingUrlId = null; editingUrl = { url: "", text: "" }; } + + const relativeCrumbs = [{ title: "FAQ" }, { title: "Edit Question" }]; - +
diff --git a/frontend/src/routes/settings/faq/question/new/+page.svelte b/frontend/src/routes/settings/faq/question/new/+page.svelte index 5dd6b4c..590b058 100644 --- a/frontend/src/routes/settings/faq/question/new/+page.svelte +++ b/frontend/src/routes/settings/faq/question/new/+page.svelte @@ -6,9 +6,11 @@ const { data, form } = $props(); let submitting = $state(false); + + const relativeCrumbs = [{ title: "FAQ", href: "/settings/faq" }, { title: "Add Question" }]; - +
diff --git a/frontend/src/routes/settings/faq/tip/[id]/edit/+page.svelte b/frontend/src/routes/settings/faq/tip/[id]/edit/+page.svelte index 9873127..be4a5d8 100644 --- a/frontend/src/routes/settings/faq/tip/[id]/edit/+page.svelte +++ b/frontend/src/routes/settings/faq/tip/[id]/edit/+page.svelte @@ -5,9 +5,11 @@ const { data, form } = $props(); let submitting = $state(false); + + const relativeCrumbs = [{ title: "FAQ", href: "/settings/faq" }, { title: "Edit Tip" }]; - +
diff --git a/frontend/src/routes/settings/faq/tip/new/+page.svelte b/frontend/src/routes/settings/faq/tip/new/+page.svelte index bdad3b1..cacd1e7 100644 --- a/frontend/src/routes/settings/faq/tip/new/+page.svelte +++ b/frontend/src/routes/settings/faq/tip/new/+page.svelte @@ -5,9 +5,11 @@ const { data, form } = $props(); let submitting = $state(false); + + const relativeCrumbs = [{ title: "FAQ", href: "/settings/faq" }, { title: "Add Tip" }]; - +
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 470dc0c..f65ae14 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -9,3 +9,9 @@ export type IComingSoon = { intro: string; expectations: IExpectations[]; }; + +export type IBreadcrumb = { + title: string; + href?: string; + icon?: string; +}; From dd991f0cd9f56d625e05d6496f31aeb0adfa865e Mon Sep 17 00:00:00 2001 From: Ian Day Date: Sun, 4 Jan 2026 16:23:37 -0500 Subject: [PATCH 5/5] feat(Deadline): :sparkles: finish deadlines in frontend --- .../management/commands/send_update_email.py | 5 +- backend/deadline/api.py | 42 ++++ backend/okletsdoit/urls.py | 2 +- docs/dev/roadmap.md | 9 +- frontend/api-client/apis/DeadlinesApi.ts | 51 +++++ frontend/api-client/docs/DeadlinesApi.md | 90 ++++++-- .../src/lib/components/layouts/Topbar.svelte | 9 +- .../lib/components/object/ObjectDetail.svelte | 27 ++- frontend/src/lib/styles/core/components.css | 2 +- .../src/routes/settings/deadline/+page.svelte | 17 +- .../settings/deadline/all/+page.server.ts | 21 ++ .../routes/settings/deadline/all/+page.svelte | 195 ++++++++++++++++++ .../deadline/deadline/[id]/+page.server.ts | 40 +++- .../deadline/deadline/[id]/+page.svelte | 16 +- 14 files changed, 487 insertions(+), 39 deletions(-) create mode 100644 frontend/src/routes/settings/deadline/all/+page.server.ts create mode 100644 frontend/src/routes/settings/deadline/all/+page.svelte diff --git a/backend/core/management/commands/send_update_email.py b/backend/core/management/commands/send_update_email.py index 59d8575..08797e5 100644 --- a/backend/core/management/commands/send_update_email.py +++ b/backend/core/management/commands/send_update_email.py @@ -8,7 +8,6 @@ from django.contrib.auth import get_user_model from django.db.models import Sum, DecimalField from django.utils import timezone -from django.urls import reverse from django.template.loader import render_to_string from core.models import WeddingSettings @@ -187,7 +186,7 @@ def _build_html_email_body(self, base_url, **data): overdue_deadlines = [] for deadline in data["overdue_deadlines"]: days_overdue = (timezone.now().date() - deadline.due_date).days - deadline_url = f"{base_url}{reverse('deadline:deadline_detail', kwargs={'deadline_slug': deadline.slug})}" + deadline_url = f"{base_url}/settings/deadline/deadline/{deadline.id}" overdue_deadlines.append( { "name": deadline.name, @@ -201,7 +200,7 @@ def _build_html_email_body(self, base_url, **data): upcoming_deadlines = [] for deadline in data["upcoming_deadlines"]: days_until = (deadline.due_date - timezone.now().date()).days - deadline_url = f"{base_url}{reverse('deadline:deadline_detail', kwargs={'deadline_slug': deadline.slug})}" + deadline_url = f"{base_url}/settings/deadline/deadline/{deadline.id}" upcoming_deadlines.append( { "name": deadline.name, diff --git a/backend/deadline/api.py b/backend/deadline/api.py index 570117b..cb08c75 100644 --- a/backend/deadline/api.py +++ b/backend/deadline/api.py @@ -337,6 +337,48 @@ def create_deadline(request, payload: DeadlineCreateSchema): } +@router.post("/deadlines/{deadline_id}/toggle_complete", response=DeadlineSchema) +def toggle_deadline_complete(request, deadline_id: UUID): + """Toggle the completion status of a deadline""" + deadline = get_object_or_404(Deadline, id=deadline_id, is_deleted=False) + deadline.completed = not deadline.completed + if deadline.completed: + deadline.completed_at = datetime.now() + else: + deadline.completed_at = None + + if request.user.is_authenticated: + deadline.updated_by = request.user + else: + admin_user = User.objects.filter(is_staff=True, is_active=True).first() + if admin_user: + deadline.updated_by = admin_user + + deadline.save() + + return { + "id": deadline.id, + "name": deadline.name, + "slug": deadline.slug, + "description": deadline.description, + "deadline_list_id": deadline.deadline_list.id if deadline.deadline_list else None, + "deadline_list_name": deadline.deadline_list.name if deadline.deadline_list else None, + "due_date": deadline.due_date, + "assigned_to_id": deadline.assigned_to.id if deadline.assigned_to else None, + "assigned_to_name": deadline.assigned_to.get_full_name() if deadline.assigned_to else None, + "completed": deadline.completed, + "completed_at": deadline.completed_at, + "completed_note": deadline.completed_note, + "overdue": deadline.overdue_status(), + "created_by_id": deadline.created_by.id if deadline.created_by else None, + "created_by_name": deadline.created_by.get_full_name() if deadline.created_by else None, + "updated_by_id": deadline.updated_by.id if deadline.updated_by else None, + "updated_by_name": deadline.updated_by.get_full_name() if deadline.updated_by else None, + "created_at": deadline.created_at, + "updated_at": deadline.updated_at, + } + + @router.put("/deadlines/{deadline_id}", response=DeadlineSchema) def update_deadline(request, deadline_id: UUID, payload: DeadlineUpdateSchema): """Update a deadline""" diff --git a/backend/okletsdoit/urls.py b/backend/okletsdoit/urls.py index 51055e2..caca700 100644 --- a/backend/okletsdoit/urls.py +++ b/backend/okletsdoit/urls.py @@ -16,7 +16,7 @@ path("expenses/", include("expenses.urls")), path("contacts/", include("contacts.urls")), path("lists/", include("list.urls")), - path("deadline/", include("deadline.urls")), + # path("deadline/", include("deadline.urls")), path("guestlist/", include("guestlist.urls")), path("attachments/", include("attachments.urls")), re_path( diff --git a/docs/dev/roadmap.md b/docs/dev/roadmap.md index 78c88e3..10669da 100644 --- a/docs/dev/roadmap.md +++ b/docs/dev/roadmap.md @@ -4,10 +4,10 @@ - [x] Settings - [X] FAQ - [ ] Deadlines - - [ ] List View - - [ ] Delete List and Item - - [ ] Toggle completion - - [ ] Disable Django components + - [X] List View + - [x] Delete List and Item + - [x] Toggle completion + - [x] Disable Django components - [ ] Guestlist - [ ] Timeline - [ ] Contacts @@ -35,6 +35,7 @@ * [ ] Photo approval (Private) - [X] Deadlines * [X] Notification of upcoming deadlines + * [ ] Deletion of deadline list and all associated deadlines - [ ] Private Documentation * [X] Settings * [ ] Dashboard diff --git a/frontend/api-client/apis/DeadlinesApi.ts b/frontend/api-client/apis/DeadlinesApi.ts index 67c5cfd..4c8b04c 100644 --- a/frontend/api-client/apis/DeadlinesApi.ts +++ b/frontend/api-client/apis/DeadlinesApi.ts @@ -78,6 +78,10 @@ export interface DeadlineApiListDeadlinesRequest { pageSize?: number | null; } +export interface DeadlineApiToggleDeadlineCompleteRequest { + deadlineId: string; +} + export interface DeadlineApiUpdateDeadlineRequest { deadlineId: string; deadlineUpdateSchema: DeadlineUpdateSchema; @@ -482,6 +486,53 @@ export class DeadlinesApi extends runtime.BaseAPI { return await response.value(); } + /** + * Toggle the completion status of a deadline + * Toggle Deadline Complete + */ + async deadlineApiToggleDeadlineCompleteRaw( + requestParameters: DeadlineApiToggleDeadlineCompleteRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if (requestParameters["deadlineId"] == null) { + throw new runtime.RequiredError( + "deadlineId", + 'Required parameter "deadlineId" was null or undefined when calling deadlineApiToggleDeadlineComplete().', + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + let urlPath = `/api/deadline/deadlines/{deadline_id}/toggle_complete`; + urlPath = urlPath.replace(`{${"deadline_id"}}`, encodeURIComponent(String(requestParameters["deadlineId"]))); + + const response = await this.request( + { + path: urlPath, + method: "POST", + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => DeadlineSchemaFromJSON(jsonValue)); + } + + /** + * Toggle the completion status of a deadline + * Toggle Deadline Complete + */ + async deadlineApiToggleDeadlineComplete( + requestParameters: DeadlineApiToggleDeadlineCompleteRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.deadlineApiToggleDeadlineCompleteRaw(requestParameters, initOverrides); + return await response.value(); + } + /** * Update a deadline * Update Deadline diff --git a/frontend/api-client/docs/DeadlinesApi.md b/frontend/api-client/docs/DeadlinesApi.md index 975e401..be1eee8 100644 --- a/frontend/api-client/docs/DeadlinesApi.md +++ b/frontend/api-client/docs/DeadlinesApi.md @@ -2,18 +2,19 @@ All URIs are relative to _http://localhost_ -| Method | HTTP request | Description | -| ---------------------------------------------------------------------------------- | ------------------------------------------------- | -------------------- | -| [**deadlineApiCreateDeadline**](DeadlinesApi.md#deadlineapicreatedeadline) | **POST** /api/deadline/deadlines | Create Deadline | -| [**deadlineApiCreateDeadlineList**](DeadlinesApi.md#deadlineapicreatedeadlinelist) | **POST** /api/deadline/deadline-lists | Create Deadline List | -| [**deadlineApiDeleteDeadline**](DeadlinesApi.md#deadlineapideletedeadline) | **DELETE** /api/deadline/deadlines/{deadline_id} | Delete Deadline | -| [**deadlineApiDeleteDeadlineList**](DeadlinesApi.md#deadlineapideletedeadlinelist) | **DELETE** /api/deadline/deadline-lists/{list_id} | Delete Deadline List | -| [**deadlineApiGetDeadline**](DeadlinesApi.md#deadlineapigetdeadline) | **GET** /api/deadline/deadlines/{deadline_id} | Get Deadline | -| [**deadlineApiGetDeadlineList**](DeadlinesApi.md#deadlineapigetdeadlinelist) | **GET** /api/deadline/deadline-lists/{list_id} | Get Deadline List | -| [**deadlineApiListDeadlineLists**](DeadlinesApi.md#deadlineapilistdeadlinelists) | **GET** /api/deadline/deadline-lists | List Deadline Lists | -| [**deadlineApiListDeadlines**](DeadlinesApi.md#deadlineapilistdeadlines) | **GET** /api/deadline/deadlines | List Deadlines | -| [**deadlineApiUpdateDeadline**](DeadlinesApi.md#deadlineapiupdatedeadline) | **PUT** /api/deadline/deadlines/{deadline_id} | Update Deadline | -| [**deadlineApiUpdateDeadlineList**](DeadlinesApi.md#deadlineapiupdatedeadlinelist) | **PUT** /api/deadline/deadline-lists/{list_id} | Update Deadline List | +| Method | HTTP request | Description | +| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ------------------------ | +| [**deadlineApiCreateDeadline**](DeadlinesApi.md#deadlineapicreatedeadline) | **POST** /api/deadline/deadlines | Create Deadline | +| [**deadlineApiCreateDeadlineList**](DeadlinesApi.md#deadlineapicreatedeadlinelist) | **POST** /api/deadline/deadline-lists | Create Deadline List | +| [**deadlineApiDeleteDeadline**](DeadlinesApi.md#deadlineapideletedeadline) | **DELETE** /api/deadline/deadlines/{deadline_id} | Delete Deadline | +| [**deadlineApiDeleteDeadlineList**](DeadlinesApi.md#deadlineapideletedeadlinelist) | **DELETE** /api/deadline/deadline-lists/{list_id} | Delete Deadline List | +| [**deadlineApiGetDeadline**](DeadlinesApi.md#deadlineapigetdeadline) | **GET** /api/deadline/deadlines/{deadline_id} | Get Deadline | +| [**deadlineApiGetDeadlineList**](DeadlinesApi.md#deadlineapigetdeadlinelist) | **GET** /api/deadline/deadline-lists/{list_id} | Get Deadline List | +| [**deadlineApiListDeadlineLists**](DeadlinesApi.md#deadlineapilistdeadlinelists) | **GET** /api/deadline/deadline-lists | List Deadline Lists | +| [**deadlineApiListDeadlines**](DeadlinesApi.md#deadlineapilistdeadlines) | **GET** /api/deadline/deadlines | List Deadlines | +| [**deadlineApiToggleDeadlineComplete**](DeadlinesApi.md#deadlineapitoggledeadlinecomplete) | **POST** /api/deadline/deadlines/{deadline_id}/toggle_complete | Toggle Deadline Complete | +| [**deadlineApiUpdateDeadline**](DeadlinesApi.md#deadlineapiupdatedeadline) | **PUT** /api/deadline/deadlines/{deadline_id} | Update Deadline | +| [**deadlineApiUpdateDeadlineList**](DeadlinesApi.md#deadlineapiupdatedeadlinelist) | **PUT** /api/deadline/deadline-lists/{list_id} | Update Deadline List | ## deadlineApiCreateDeadline @@ -547,6 +548,71 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md) +## deadlineApiToggleDeadlineComplete + +> DeadlineSchema deadlineApiToggleDeadlineComplete(deadlineId) + +Toggle Deadline Complete + +Toggle the completion status of a deadline + +### Example + +```ts +import { + Configuration, + DeadlinesApi, +} from ''; +import type { DeadlineApiToggleDeadlineCompleteRequest } from ''; + +async function example() { + console.log("🚀 Testing SDK..."); + const api = new DeadlinesApi(); + + const body = { + // string + deadlineId: 38400000-8cf0-11bd-b23e-10b96e4ef00d, + } satisfies DeadlineApiToggleDeadlineCompleteRequest; + + try { + const data = await api.deadlineApiToggleDeadlineComplete(body); + console.log(data); + } catch (error) { + console.error(error); + } +} + +// Run the test +example().catch(console.error); +``` + +### Parameters + +| Name | Type | Description | Notes | +| -------------- | -------- | ----------- | ------------------------- | +| **deadlineId** | `string` | | [Defaults to `undefined`] | + +### Return type + +[**DeadlineSchema**](DeadlineSchema.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: `application/json` + +### HTTP response details + +| Status code | Description | Response headers | +| ----------- | ----------- | ---------------- | +| **200** | OK | - | + +[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md) + ## deadlineApiUpdateDeadline > DeadlineSchema deadlineApiUpdateDeadline(deadlineId, deadlineUpdateSchema) diff --git a/frontend/src/lib/components/layouts/Topbar.svelte b/frontend/src/lib/components/layouts/Topbar.svelte index b0e3152..81a4dae 100644 --- a/frontend/src/lib/components/layouts/Topbar.svelte +++ b/frontend/src/lib/components/layouts/Topbar.svelte @@ -35,6 +35,7 @@ const protectedMenu: IMenuItem[] = [ { title: "FAQ", href: "/settings/faq", icon: "icon-[lucide--help-circle]" }, + { title: "Deadlines", href: "/settings/deadline", icon: "icon-[lucide--calendar-check]" }, { title: "Settings", href: "/settings/config", @@ -86,10 +87,10 @@ title: "Lists", href: "/lists/", }, - { - title: "Deadlines", - href: "/deadline/", - }, + // { + // title: "Deadlines", + // href: "/deadline/", + // }, { title: "Budget", href: "/expenses/", diff --git a/frontend/src/lib/components/object/ObjectDetail.svelte b/frontend/src/lib/components/object/ObjectDetail.svelte index 0f19cfb..9073b88 100644 --- a/frontend/src/lib/components/object/ObjectDetail.svelte +++ b/frontend/src/lib/components/object/ObjectDetail.svelte @@ -10,6 +10,8 @@ status: boolean; statusText: string; editLink: string; + deleteAction?: string; + deleteRedirect?: string; childCreateLink?: string; childCreateLabel?: string; mainSnippet: Snippet; @@ -24,6 +26,8 @@ status, statusText, editLink, + deleteAction, + deleteRedirect, childCreateLink, childCreateLabel, mainSnippet, @@ -41,7 +45,21 @@
Edit - + {#if deleteAction} +
{ + if (!confirm("Are you sure you want to delete this item? This action cannot be undone.")) { + e.preventDefault(); + } + }}> + {#if deleteRedirect} + + {/if} + +
+ {/if}
@@ -49,11 +67,10 @@
Item Details
{@render mainSnippet()} - - {#if mainActionsSnippet} -
{@render mainActionsSnippet()}
- {/if}
+ {#if mainActionsSnippet} +
{@render mainActionsSnippet()}
+ {/if}
diff --git a/frontend/src/lib/styles/core/components.css b/frontend/src/lib/styles/core/components.css index acdbabe..de19e72 100644 --- a/frontend/src/lib/styles/core/components.css +++ b/frontend/src/lib/styles/core/components.css @@ -199,7 +199,7 @@ input:-webkit-autofill:active { } .detail-card-actions { - @apply card-actions mt-4 justify-center space-x-4; + @apply card-actions mt-4 justify-center pb-4; } .detail-action-card { diff --git a/frontend/src/routes/settings/deadline/+page.svelte b/frontend/src/routes/settings/deadline/+page.svelte index 03d1239..438590b 100644 --- a/frontend/src/routes/settings/deadline/+page.svelte +++ b/frontend/src/routes/settings/deadline/+page.svelte @@ -14,24 +14,29 @@

Deadline Summary

Manage your wedding planning deadlines and tasks

- - - New List - +
{#if data.deadlineLists.length === 0}
- +

No deadline lists yet

Create your first deadline list to start organizing your wedding planning tasks and staying on track.

- + Create First List
diff --git a/frontend/src/routes/settings/deadline/all/+page.server.ts b/frontend/src/routes/settings/deadline/all/+page.server.ts new file mode 100644 index 0000000..b12fac1 --- /dev/null +++ b/frontend/src/routes/settings/deadline/all/+page.server.ts @@ -0,0 +1,21 @@ +import { api } from "$lib/server/api-client"; +import type { PageServerLoad } from "../deadline/all/$types"; + +export const load: PageServerLoad = async ({ url }) => { + const deadlineListId = url.searchParams.get("list"); + const page = parseInt(url.searchParams.get("page") || "1", 10); + const pageSize = parseInt(url.searchParams.get("pageSize") || "50", 10); + + const deadlines = await api.deadlines.deadlineApiListDeadlines({ + deadlineListId: deadlineListId || undefined, + page, + pageSize, + }); + + return { + deadlines, + filterListId: deadlineListId, + currentPage: page, + pageSize, + }; +}; diff --git a/frontend/src/routes/settings/deadline/all/+page.svelte b/frontend/src/routes/settings/deadline/all/+page.svelte new file mode 100644 index 0000000..cfbf8a9 --- /dev/null +++ b/frontend/src/routes/settings/deadline/all/+page.svelte @@ -0,0 +1,195 @@ + + + + +
+
+

All Deadlines

+

+ {#if data.filterListId} + Showing deadlines from selected list + {:else} + View and manage all your wedding planning deadlines + {/if} +

+
+
+ {#if data.filterListId} + + + Clear Filter + + {/if} + + + Back to Lists + +
+
+ + {#if data.deadlines.items.length === 0} + +
+
+ +

No deadlines found

+

+ {#if data.filterListId} + This list doesn't have any deadlines yet. Add some to get started! + {:else} + You haven't created any deadlines yet. Start by creating a deadline list. + {/if} +

+ + + Go to Deadline Lists + +
+
+ {:else} + +
+ {#each data.deadlines.items as deadline (deadline.id)} +
+
+
+
+
+

+ + {deadline.name} + +

+ {#if deadline.completed} + + + Completed + + {:else if deadline.overdue} + + + Overdue + + {:else} + + + Pending + + {/if} +
+ + {#if deadline.description} +

{deadline.description}

+ {/if} + +
+ {#if deadline.deadlineListName} + + {/if} + +
+ + {formatDate(deadline.dueDate)} +
+ + {#if deadline.assignedToName} +
+ + {deadline.assignedToName} +
+ {/if} + + {#if deadline.completed && deadline.completedAt} +
+ + Completed {formatDate(deadline.completedAt)} +
+ {/if} +
+
+ + + + +
+
+
+ {/each} +
+ + + {#if data.deadlines.count > data.pageSize} + {@const totalPages = Math.ceil(data.deadlines.count / data.pageSize)} + {@const hasPrevious = data.currentPage > 1} + {@const hasNext = data.currentPage < totalPages} + {@const baseUrl = data.filterListId + ? `/settings/deadline/all?list=${data.filterListId}` + : "/settings/deadline/all"} + +
+
+ Showing {(data.currentPage - 1) * data.pageSize + 1} - {Math.min( + data.currentPage * data.pageSize, + data.deadlines.count, + )} of {data.deadlines.count} deadlines +
+ +
+ + + Previous + + + + + + Next + + +
+
+ {/if} + {/if} +
diff --git a/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts b/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts index 0a507ea..d8f590a 100644 --- a/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts +++ b/frontend/src/routes/settings/deadline/deadline/[id]/+page.server.ts @@ -1,6 +1,6 @@ import { api } from "$lib/server/api-client"; -import { error } from "@sveltejs/kit"; -import type { PageServerLoad } from "./$types"; +import { error, fail, redirect } from "@sveltejs/kit"; +import type { Actions, PageServerLoad } from "./$types"; export const load: PageServerLoad = async ({ params }) => { try { @@ -16,3 +16,39 @@ export const load: PageServerLoad = async ({ params }) => { throw error(404, "Deadline not found"); } }; + +export const actions = { + toggleComplete: async ({ params }) => { + try { + await api.deadlines.deadlineApiToggleDeadlineComplete({ + deadlineId: params.id, + }); + } catch (err) { + console.error("Failed to toggle deadline completion:", err); + return fail(500, { error: "Failed to toggle completion status" }); + } + + return { success: true }; + }, + delete: async ({ params }) => { + let deadline; + try { + deadline = await api.deadlines.deadlineApiGetDeadline({ + deadlineId: params.id, + }); + + await api.deadlines.deadlineApiDeleteDeadline({ + deadlineId: params.id, + }); + } catch (err) { + console.error("Failed to delete deadline:", err); + return fail(500, { error: "Failed to delete deadline" }); + } + + const redirectPath = deadline.deadlineListId + ? `/settings/deadline/list/${deadline.deadlineListId}` + : "/settings/deadline"; + + throw redirect(303, redirectPath); + }, +} satisfies Actions; diff --git a/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte b/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte index d148d38..78f4c23 100644 --- a/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte +++ b/frontend/src/routes/settings/deadline/deadline/[id]/+page.svelte @@ -41,6 +41,7 @@ {status} {statusText} editLink={`/settings/deadline/deadline/${data.deadline.id}/edit`} + deleteAction="?/delete" object={data.deadline}> {#snippet mainSnippet()}
@@ -100,8 +101,21 @@ {/snippet} {#snippet mainActionsSnippet()} +
+ +
- + Edit {/snippet}