diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7784da7..c377a26 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,6 +11,17 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install dependencies + run: npm ci + + - name: Run migrations + run: | + set -a + source /mnt/volume_nyc3_01/Portal/.env + set +a + export DO_CA_CERTIFICATE=/etc/ssl/certs/do-ca-certificate.crt + npx prisma migrate deploy + - name: Build image run: podman build -t portal:latest . diff --git a/prisma/migrations/20260223163626_add_category_parent/migration.sql b/prisma/migrations/20260223163626_add_category_parent/migration.sql new file mode 100644 index 0000000..000d5e9 --- /dev/null +++ b/prisma/migrations/20260223163626_add_category_parent/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "AffirmationCategory" ADD COLUMN "parentId" INTEGER; + +-- AddForeignKey +ALTER TABLE "AffirmationCategory" ADD CONSTRAINT "AffirmationCategory_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "AffirmationCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e3eecfc..afb4ae2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -128,8 +128,11 @@ model SolfeggioFrequency { } model AffirmationCategory { - id Int @id @default(autoincrement()) - name String @unique + id Int @id @default(autoincrement()) + name String @unique + parentId Int? + parent AffirmationCategory? @relation("CategoryHierarchy", fields: [parentId], references: [id]) + children AffirmationCategory[] @relation("CategoryHierarchy") affirmations Affirmation[] } diff --git a/src/lib/components/admin/AdminTable.svelte b/src/lib/components/admin/AdminTable.svelte index 2950893..e2a8b73 100644 --- a/src/lib/components/admin/AdminTable.svelte +++ b/src/lib/components/admin/AdminTable.svelte @@ -110,7 +110,7 @@ class="flex items-center gap-1 hover:text-gray-900" onclick={() => toggleSort(field)} > - {fieldNameToLabel(field.includes('.') ? (field.split('.').pop() ?? field) : field)} + {fieldNameToLabel(field.includes('.') ? field.split('.')[0] : field)} {getSortIndicator(field)} diff --git a/src/lib/components/forms/affirmation/AffirmationForm.svelte b/src/lib/components/forms/affirmation/AffirmationForm.svelte index 7e0c52a..9a53799 100644 --- a/src/lib/components/forms/affirmation/AffirmationForm.svelte +++ b/src/lib/components/forms/affirmation/AffirmationForm.svelte @@ -4,6 +4,8 @@ type Category = { id: string; name: string; + parentId?: number | null; + children?: Category[]; }; type Frequency = { @@ -18,6 +20,7 @@ price?: number; categoryId?: string; solfeggioFrequencyId?: string; + category?: { parentId?: number | null; parent?: { name: string } }; }; type FormData = { @@ -27,6 +30,7 @@ price?: number; categoryId?: string; categoryName?: string; + subCategoryName?: string; solfeggioFrequencyId?: string; }; @@ -48,11 +52,74 @@ submitLabel = 'Save Affirmation' }: Props = $props(); - const currentCategory = $derived( - categories.find((c : Category) => c.id === (form?.categoryId ?? affirmation.categoryId)) - ); + const topLevelCategories = $derived(categories.filter((c) => !c.parentId)); - let categoryInput = $derived(form?.categoryName ?? currentCategory?.name ?? ''); + // Determine initial parent category name from form data or existing affirmation + function getInitialCategoryName(): string { + if (form?.categoryName) return form.categoryName; + if (affirmation.category?.parent) return affirmation.category.parent.name; + if (affirmation.categoryId) { + const cat = categories.find((c) => c.id === affirmation.categoryId); + if (cat?.parentId) { + const parent = categories.find((c) => String(c.id) === String(cat.parentId)); + return parent?.name ?? ''; + } + return cat?.name ?? ''; + } + return ''; + } + + function getInitialSubCategoryName(): string { + if (form?.subCategoryName) return form.subCategoryName; + if (affirmation.categoryId) { + const cat = categories.find((c) => c.id === affirmation.categoryId); + if (cat?.parentId) return cat.name; + } + return ''; + } + + let categoryInput = $state(getInitialCategoryName()); + let showNewCategory = $state(false); + let newCategoryName = $state(''); + + let subCategoryInput = $state(getInitialSubCategoryName()); + let showNewSubCategory = $state(false); + let newSubCategoryName = $state(''); + + const selectedParent = $derived(topLevelCategories.find((c) => c.name === categoryInput)); + const subCategories = $derived(selectedParent?.children ?? []); + + function handleCategoryChange(e: Event) { + const value = (e.target as HTMLSelectElement).value; + if (value === '__new__') { + showNewCategory = true; + categoryInput = ''; + } else { + showNewCategory = false; + newCategoryName = ''; + categoryInput = value; + } + subCategoryInput = ''; + showNewSubCategory = false; + newSubCategoryName = ''; + } + + function handleSubCategoryChange(e: Event) { + const value = (e.target as HTMLSelectElement).value; + if (value === '__new__') { + showNewSubCategory = true; + subCategoryInput = ''; + } else { + showNewSubCategory = false; + newSubCategoryName = ''; + subCategoryInput = value; + } + } + + const effectiveCategoryName = $derived(showNewCategory ? newCategoryName : categoryInput); + const effectiveSubCategoryName = $derived( + showNewSubCategory ? newSubCategoryName : subCategoryInput + );