Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin ordering of items #161

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
"flowbite-svelte-icons": "2.0.2",
"globals": "^15.12.0",
"jsdom": "^25.0.1",
"svelte": "^5.1.14",
"svelte-check": "^4.0.6",
"svelte": "^5.1.16",
"svelte-check": "^4.0.7",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"typescript-eslint": "^8.13.0",
"typescript-eslint": "^8.14.0",
"vite": "^5.4.11",
"vitest": "^2.1.4"
},
Expand All @@ -39,6 +39,7 @@
"@hey-api/client-fetch": "0.4.3",
"@unovis/ts": "1.5.0-beta.0",
"iso-639-1": "3.1.3",
"svelte-dnd-action": "^0.9.52",
"svelte-i18n": "^4.0.1"
}
}
446 changes: 229 additions & 217 deletions frontend/pnpm-lock.yaml

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions frontend/src/lib/client/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,22 @@ export const HTTPValidationErrorSchema = {
title: 'HTTPValidationError'
} as const;

export const ItemOrderSchema = {
properties: {
id: {
type: 'integer',
title: 'Id'
},
order: {
type: 'integer',
title: 'Order'
}
},
type: 'object',
required: ['id', 'order'],
title: 'ItemOrder'
} as const;

export const LanguageSchema = {
properties: {
id: {
Expand Down
42 changes: 41 additions & 1 deletion frontend/src/lib/client/services.gen.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This file is auto-generated by @hey-api/openapi-ts

import { createClient, createConfig, type Options, formDataBodySerializer, urlSearchParamsBodySerializer } from '@hey-api/client-fetch';
import type { GetLanguagesError, GetLanguagesResponse, GetMilestonesError, GetMilestonesResponse, GetMilestoneData, GetMilestoneError, GetMilestoneResponse, GetMilestoneGroupsData, GetMilestoneGroupsError, GetMilestoneGroupsResponse, GetUserQuestionsError, GetUserQuestionsResponse, GetChildQuestionsError, GetChildQuestionsResponse, CreateLanguageData, CreateLanguageError, CreateLanguageResponse, DeleteLanguageData, DeleteLanguageError, DeleteLanguageResponse, UpdateI18NData, UpdateI18NError, UpdateI18NResponse, GetMilestoneGroupsAdminError, GetMilestoneGroupsAdminResponse, CreateMilestoneGroupAdminError, CreateMilestoneGroupAdminResponse, UpdateMilestoneGroupAdminData, UpdateMilestoneGroupAdminError, UpdateMilestoneGroupAdminResponse, DeleteMilestoneGroupAdminData, DeleteMilestoneGroupAdminError, DeleteMilestoneGroupAdminResponse, UploadMilestoneGroupImageData, UploadMilestoneGroupImageError, UploadMilestoneGroupImageResponse, CreateMilestoneData, CreateMilestoneError, CreateMilestoneResponse, UpdateMilestoneData, UpdateMilestoneError, UpdateMilestoneResponse, DeleteMilestoneData, DeleteMilestoneError, DeleteMilestoneResponse, UploadMilestoneImageData, UploadMilestoneImageError, UploadMilestoneImageResponse, DeleteMilestoneImageData, DeleteMilestoneImageError, DeleteMilestoneImageResponse, GetMilestoneAgeScoresData, GetMilestoneAgeScoresError, GetMilestoneAgeScoresResponse, GetUserQuestionsAdminError, GetUserQuestionsAdminResponse, UpdateUserQuestionData, UpdateUserQuestionError, UpdateUserQuestionResponse, CreateUserQuestionError, CreateUserQuestionResponse, DeleteUserQuestionData, DeleteUserQuestionError, DeleteUserQuestionResponse, GetChildQuestionsAdminError, GetChildQuestionsAdminResponse, UpdateChildQuestionData, UpdateChildQuestionError, UpdateChildQuestionResponse, CreateChildQuestionError, CreateChildQuestionResponse, DeleteChildQuestionData, DeleteChildQuestionError, DeleteChildQuestionResponse, UsersCurrentUserError, UsersCurrentUserResponse, UsersPatchCurrentUserData, UsersPatchCurrentUserError, UsersPatchCurrentUserResponse, UsersUserData, UsersUserError, UsersUserResponse, UsersPatchUserData, UsersPatchUserError, UsersPatchUserResponse, UsersDeleteUserData, UsersDeleteUserError, UsersDeleteUserResponse, GetChildrenError, GetChildrenResponse, UpdateChildData, UpdateChildError, UpdateChildResponse, CreateChildData, CreateChildError, CreateChildResponse, GetChildData, GetChildError, GetChildResponse, DeleteChildData, DeleteChildError, DeleteChildResponse, GetChildImageData, GetChildImageError, GetChildImageResponse, UploadChildImageData, UploadChildImageError, UploadChildImageResponse, DeleteChildImageData, DeleteChildImageError, DeleteChildImageResponse, GetCurrentMilestoneAnswerSessionData, GetCurrentMilestoneAnswerSessionError, GetCurrentMilestoneAnswerSessionResponse, UpdateMilestoneAnswerData, UpdateMilestoneAnswerError, UpdateMilestoneAnswerResponse, GetCurrentUserAnswersError, GetCurrentUserAnswersResponse, UpdateCurrentUserAnswersData, UpdateCurrentUserAnswersError, UpdateCurrentUserAnswersResponse, GetCurrentChildAnswersData, GetCurrentChildAnswersError, GetCurrentChildAnswersResponse, UpdateCurrentChildAnswersData, UpdateCurrentChildAnswersError, UpdateCurrentChildAnswersResponse, AuthCookieLoginData, AuthCookieLoginError, AuthCookieLoginResponse, AuthCookieLogoutError, AuthCookieLogoutResponse, RegisterRegisterData, RegisterRegisterError, RegisterRegisterResponse, ResetForgotPasswordData, ResetForgotPasswordError, ResetForgotPasswordResponse, ResetResetPasswordData, ResetResetPasswordError, ResetResetPasswordResponse, VerifyRequestTokenData, VerifyRequestTokenError, VerifyRequestTokenResponse, VerifyVerifyData, VerifyVerifyError, VerifyVerifyResponse, AuthError, AuthResponse } from './types.gen';
import type { GetLanguagesError, GetLanguagesResponse, GetMilestonesError, GetMilestonesResponse, GetMilestoneData, GetMilestoneError, GetMilestoneResponse, GetMilestoneGroupsData, GetMilestoneGroupsError, GetMilestoneGroupsResponse, GetUserQuestionsError, GetUserQuestionsResponse, GetChildQuestionsError, GetChildQuestionsResponse, CreateLanguageData, CreateLanguageError, CreateLanguageResponse, DeleteLanguageData, DeleteLanguageError, DeleteLanguageResponse, UpdateI18NData, UpdateI18NError, UpdateI18NResponse, GetMilestoneGroupsAdminError, GetMilestoneGroupsAdminResponse, CreateMilestoneGroupAdminError, CreateMilestoneGroupAdminResponse, UpdateMilestoneGroupAdminData, UpdateMilestoneGroupAdminError, UpdateMilestoneGroupAdminResponse, DeleteMilestoneGroupAdminData, DeleteMilestoneGroupAdminError, DeleteMilestoneGroupAdminResponse, OrderMilestoneGroupsAdminData, OrderMilestoneGroupsAdminError, OrderMilestoneGroupsAdminResponse, UploadMilestoneGroupImageData, UploadMilestoneGroupImageError, UploadMilestoneGroupImageResponse, CreateMilestoneData, CreateMilestoneError, CreateMilestoneResponse, UpdateMilestoneData, UpdateMilestoneError, UpdateMilestoneResponse, DeleteMilestoneData, DeleteMilestoneError, DeleteMilestoneResponse, OrderMilestonesAdminData, OrderMilestonesAdminError, OrderMilestonesAdminResponse, UploadMilestoneImageData, UploadMilestoneImageError, UploadMilestoneImageResponse, DeleteMilestoneImageData, DeleteMilestoneImageError, DeleteMilestoneImageResponse, GetMilestoneAgeScoresData, GetMilestoneAgeScoresError, GetMilestoneAgeScoresResponse, GetUserQuestionsAdminError, GetUserQuestionsAdminResponse, UpdateUserQuestionData, UpdateUserQuestionError, UpdateUserQuestionResponse, CreateUserQuestionError, CreateUserQuestionResponse, DeleteUserQuestionData, DeleteUserQuestionError, DeleteUserQuestionResponse, OrderUserQuestionsAdminData, OrderUserQuestionsAdminError, OrderUserQuestionsAdminResponse, GetChildQuestionsAdminError, GetChildQuestionsAdminResponse, UpdateChildQuestionData, UpdateChildQuestionError, UpdateChildQuestionResponse, CreateChildQuestionError, CreateChildQuestionResponse, DeleteChildQuestionData, DeleteChildQuestionError, DeleteChildQuestionResponse, OrderChildQuestionsAdminData, OrderChildQuestionsAdminError, OrderChildQuestionsAdminResponse, UsersCurrentUserError, UsersCurrentUserResponse, UsersPatchCurrentUserData, UsersPatchCurrentUserError, UsersPatchCurrentUserResponse, UsersUserData, UsersUserError, UsersUserResponse, UsersPatchUserData, UsersPatchUserError, UsersPatchUserResponse, UsersDeleteUserData, UsersDeleteUserError, UsersDeleteUserResponse, GetChildrenError, GetChildrenResponse, UpdateChildData, UpdateChildError, UpdateChildResponse, CreateChildData, CreateChildError, CreateChildResponse, GetChildData, GetChildError, GetChildResponse, DeleteChildData, DeleteChildError, DeleteChildResponse, GetChildImageData, GetChildImageError, GetChildImageResponse, UploadChildImageData, UploadChildImageError, UploadChildImageResponse, DeleteChildImageData, DeleteChildImageError, DeleteChildImageResponse, GetCurrentMilestoneAnswerSessionData, GetCurrentMilestoneAnswerSessionError, GetCurrentMilestoneAnswerSessionResponse, UpdateMilestoneAnswerData, UpdateMilestoneAnswerError, UpdateMilestoneAnswerResponse, GetCurrentUserAnswersError, GetCurrentUserAnswersResponse, UpdateCurrentUserAnswersData, UpdateCurrentUserAnswersError, UpdateCurrentUserAnswersResponse, GetCurrentChildAnswersData, GetCurrentChildAnswersError, GetCurrentChildAnswersResponse, UpdateCurrentChildAnswersData, UpdateCurrentChildAnswersError, UpdateCurrentChildAnswersResponse, AuthCookieLoginData, AuthCookieLoginError, AuthCookieLoginResponse, AuthCookieLogoutError, AuthCookieLogoutResponse, RegisterRegisterData, RegisterRegisterError, RegisterRegisterResponse, ResetForgotPasswordData, ResetForgotPasswordError, ResetForgotPasswordResponse, ResetResetPasswordData, ResetResetPasswordError, ResetResetPasswordResponse, VerifyRequestTokenData, VerifyRequestTokenError, VerifyRequestTokenResponse, VerifyVerifyData, VerifyVerifyError, VerifyVerifyResponse, AuthError, AuthResponse } from './types.gen';

export const client = createClient(createConfig());

Expand Down Expand Up @@ -135,6 +135,16 @@ export const deleteMilestoneGroupAdmin = <ThrowOnError extends boolean = false>(
});
};

/**
* Order Milestone Groups Admin
*/
export const orderMilestoneGroupsAdmin = <ThrowOnError extends boolean = false>(options: Options<OrderMilestoneGroupsAdminData, ThrowOnError>) => {
return (options?.client ?? client).post<OrderMilestoneGroupsAdminResponse, OrderMilestoneGroupsAdminError, ThrowOnError>({
...options,
url: '/admin/milestone-groups/order/'
});
};

/**
* Upload Milestone Group Image
*/
Expand Down Expand Up @@ -180,6 +190,16 @@ export const deleteMilestone = <ThrowOnError extends boolean = false>(options: O
});
};

/**
* Order Milestones Admin
*/
export const orderMilestonesAdmin = <ThrowOnError extends boolean = false>(options: Options<OrderMilestonesAdminData, ThrowOnError>) => {
return (options?.client ?? client).post<OrderMilestonesAdminResponse, OrderMilestonesAdminError, ThrowOnError>({
...options,
url: '/admin/milestones/order/'
});
};

/**
* Upload Milestone Image
*/
Expand Down Expand Up @@ -255,6 +275,16 @@ export const deleteUserQuestion = <ThrowOnError extends boolean = false>(options
});
};

/**
* Order User Questions Admin
*/
export const orderUserQuestionsAdmin = <ThrowOnError extends boolean = false>(options: Options<OrderUserQuestionsAdminData, ThrowOnError>) => {
return (options?.client ?? client).post<OrderUserQuestionsAdminResponse, OrderUserQuestionsAdminError, ThrowOnError>({
...options,
url: '/admin/user-questions/order/'
});
};

/**
* Get Child Questions Admin
*/
Expand Down Expand Up @@ -295,6 +325,16 @@ export const deleteChildQuestion = <ThrowOnError extends boolean = false>(option
});
};

/**
* Order Child Questions Admin
*/
export const orderChildQuestionsAdmin = <ThrowOnError extends boolean = false>(options: Options<OrderChildQuestionsAdminData, ThrowOnError>) => {
return (options?.client ?? client).post<OrderChildQuestionsAdminResponse, OrderChildQuestionsAdminError, ThrowOnError>({
...options,
url: '/admin/child-questions/order/'
});
};

/**
* Users:Current User
*/
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/lib/client/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export type HTTPValidationError = {
detail?: Array<ValidationError>;
};

export type ItemOrder = {
id: number;
order: number;
};

export type Language = {
id: string;
};
Expand Down Expand Up @@ -374,6 +379,14 @@ export type DeleteMilestoneGroupAdminResponse = (unknown);

export type DeleteMilestoneGroupAdminError = (HTTPValidationError);

export type OrderMilestoneGroupsAdminData = {
body: Array<ItemOrder>;
};

export type OrderMilestoneGroupsAdminResponse = (unknown);

export type OrderMilestoneGroupsAdminError = (HTTPValidationError);

export type UploadMilestoneGroupImageData = {
body: Body_upload_milestone_group_image_admin_milestone_group_images__milestone_group_id__put;
path: {
Expand Down Expand Up @@ -413,6 +426,14 @@ export type DeleteMilestoneResponse = (unknown);

export type DeleteMilestoneError = (HTTPValidationError);

export type OrderMilestonesAdminData = {
body: Array<ItemOrder>;
};

export type OrderMilestonesAdminResponse = (unknown);

export type OrderMilestonesAdminError = (HTTPValidationError);

export type UploadMilestoneImageData = {
body: Body_upload_milestone_image_admin_milestone_images__milestone_id__post;
path: {
Expand Down Expand Up @@ -470,6 +491,14 @@ export type DeleteUserQuestionResponse = (unknown);

export type DeleteUserQuestionError = (HTTPValidationError);

export type OrderUserQuestionsAdminData = {
body: Array<ItemOrder>;
};

export type OrderUserQuestionsAdminResponse = (unknown);

export type OrderUserQuestionsAdminError = (HTTPValidationError);

export type GetChildQuestionsAdminResponse = (Array<ChildQuestionAdmin>);

export type GetChildQuestionsAdminError = unknown;
Expand All @@ -496,6 +525,14 @@ export type DeleteChildQuestionResponse = (unknown);

export type DeleteChildQuestionError = (HTTPValidationError);

export type OrderChildQuestionsAdminData = {
body: Array<ItemOrder>;
};

export type OrderChildQuestionsAdminResponse = (unknown);

export type OrderChildQuestionsAdminError = (HTTPValidationError);

export type UsersCurrentUserResponse = (UserRead);

export type UsersCurrentUserError = (unknown);
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/lib/components/Admin/AddButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import PlusOutline from "flowbite-svelte-icons/PlusOutline.svelte";
import Button from "flowbite-svelte/Button.svelte";
import { _ } from "svelte-i18n";

let { onclick, disabled = false }: { onclick: () => void; disabled?: boolean } =
$props();
let {
onclick,
disabled = false,
}: { onclick: (event: Event) => void; disabled?: boolean } = $props();
</script>

<Button color="blue" {onclick} {disabled}
Expand Down
25 changes: 24 additions & 1 deletion frontend/src/lib/components/Admin/MilestoneGroups.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
createMilestoneGroupAdmin,
deleteMilestone,
deleteMilestoneGroupAdmin,
orderMilestoneGroupsAdmin,
orderMilestonesAdmin,
} from "$lib/client/services.gen";
import type {
MilestoneAdmin,
Expand All @@ -21,6 +23,8 @@ import DeleteModal from "$lib/components/Admin/DeleteModal.svelte";
import EditButton from "$lib/components/Admin/EditButton.svelte";
import EditMilestoneGroupModal from "$lib/components/Admin/EditMilestoneGroupModal.svelte";
import EditMilestoneModal from "$lib/components/Admin/EditMilestoneModal.svelte";
import OrderItemsModal from "$lib/components/Admin/OrderItemsModal.svelte";
import ReorderButton from "$lib/components/Admin/ReorderButton.svelte";
import { milestoneGroups } from "$lib/stores/adminStore";
import {
Card,
Expand All @@ -33,7 +37,6 @@ import {
} from "flowbite-svelte";
import ChevronDownOutline from "flowbite-svelte-icons/ChevronDownOutline.svelte";
import ChevronUpOutline from "flowbite-svelte-icons/ChevronUpOutline.svelte";
import { onMount } from "svelte";
import { _, locale } from "svelte-i18n";

let currentMilestoneGroup = $state(null as MilestoneGroupAdmin | null);
Expand All @@ -45,6 +48,10 @@ let currentMilestone = $state(null as MilestoneAdmin | null);
let showEditMilestoneModal = $state(false);
let showDeleteMilestoneModal = $state(false);

let currentOrderEndpoint = $state(orderMilestonesAdmin);
let currentOrderItems = $state([] as Array<{ id: number; text: string }>);
let showOrderItemsModal = $state(false);

function toggleOpenGroupIndex(index: number) {
if (openMilestoneGroupIndex == index) {
openMilestoneGroupIndex = null;
Expand Down Expand Up @@ -216,6 +223,13 @@ async function doDeleteMilestone() {
<TableBodyCell></TableBodyCell>
<TableBodyCell>
<AddButton onclick={() => addMilestone(milestoneGroup.id)} />
<ReorderButton
onclick={(event: Event) => {
event.stopPropagation();
currentOrderEndpoint = orderMilestonesAdmin;
currentOrderItems = milestoneGroup.milestones.map((milestone) => {return {id: milestone.id, text: milestone.text[$locale]?.title};});
showOrderItemsModal = true;
}} />
</TableBodyCell>
</TableBodyRow>
</TableBody>
Expand All @@ -230,6 +244,13 @@ async function doDeleteMilestone() {
<TableBodyCell></TableBodyCell>
<TableBodyCell>
<AddButton onclick={addMilestoneGroup} />
<ReorderButton
onclick={(event: Event) => {
event.stopPropagation();
currentOrderEndpoint = orderMilestoneGroupsAdmin;
currentOrderItems = $milestoneGroups.map((milestoneGroup) => {return {id: milestoneGroup.id, text: milestoneGroup.text[$locale]?.title};});
showOrderItemsModal = true;
}} />
</TableBodyCell>
</TableBodyRow>
</TableBody>
Expand All @@ -251,3 +272,5 @@ async function doDeleteMilestone() {
></EditMilestoneModal>
{/key}
<DeleteModal bind:open={showDeleteMilestoneModal} onclick={doDeleteMilestone}></DeleteModal>

<OrderItemsModal bind:open={showOrderItemsModal} items={currentOrderItems} endpoint={currentOrderEndpoint} callback={refreshMilestoneGroups} />
61 changes: 61 additions & 0 deletions frontend/src/lib/components/Admin/OrderItemsModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<svelte:options runes={true}/>

<script lang="ts">
import { dndzone } from "svelte-dnd-action";
import type { DndEvent } from "svelte-dnd-action";
import { flip } from "svelte/animate";

const flipDurationMs = 100;

function handleDnd(e: CustomEvent<DndEvent>) {
items = e.detail.items as Array<Item>;
}

import { type ItemOrder } from "$lib/client/types.gen";
import CancelButton from "$lib/components/Admin/CancelButton.svelte";
import SaveButton from "$lib/components/Admin/SaveButton.svelte";
import { Modal } from "flowbite-svelte";
import { _ } from "svelte-i18n";

type Item = { id: number; text: string };

let {
open = $bindable(false),
items,
endpoint,
callback,
}: {
open: boolean;
items: Array<Item>;
endpoint: (options: any) => Promise<any>;
callback: () => Promise<any>;
} = $props();

async function post() {
const { data, error } = await endpoint({
body: items.map((value, index) => {
return { id: value.id, order: index } as ItemOrder;
}),
});
if (error) {
console.log(error);
} else {
await callback();
}
}
</script>

<Modal title={$_('admin.reorder')} bind:open autoclose outsideclose size="lg">
<section use:dndzone="{{items, flipDurationMs}}" on:consider={handleDnd} on:finalize={handleDnd}>
{#each items as item(item.id)}
<div animate:flip="{{duration: flipDurationMs}}"
class="border border-1 m-2 p-2 px-4 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700">
{item.text}
</div>
{/each}
</section>
<svelte:fragment slot="footer">
<SaveButton onclick={post}/>
<CancelButton/>
</svelte:fragment>
</Modal>
Loading