diff --git a/src/app.html b/src/app.html index 9dd3c3bd..a6bf62ad 100644 --- a/src/app.html +++ b/src/app.html @@ -6,25 +6,9 @@ - - %sveltekit.head% -
- -
-
- -
%sveltekit.body%
- - +
%sveltekit.body%
diff --git a/src/lib/admin/src/FilterWithValue.ts b/src/lib/admin/src/FilterWithValue.ts index 836a9c96..159309e1 100644 --- a/src/lib/admin/src/FilterWithValue.ts +++ b/src/lib/admin/src/FilterWithValue.ts @@ -16,7 +16,7 @@ export default class FilterWithValue { return new FilterWithValue(filter.name, filter.type, value); } - public static fromSerialized(filter: object): FilterWithValue { + public static fromSerialized(filter: Partial): FilterWithValue { if (!filter.name || !filter.type || !filter.value) { console.error('Serialized filter is incomplete', filter); diff --git a/src/lib/crud/Dashboard.ts b/src/lib/crud/Dashboard.ts index c49386b4..0c3179be 100644 --- a/src/lib/crud/Dashboard.ts +++ b/src/lib/crud/Dashboard.ts @@ -1,11 +1,12 @@ +import { DashboardDefinition, UrlAction } from '@orbitale/svelte-admin'; import Home from 'carbon-icons-svelte/lib/Home.svelte'; -import { DashboardDefinition, UrlAction } from '@orbitale/svelte-admin'; +import OperationCrud from '$lib/crud/cruds/OperationCrud'; export const dashboard = new DashboardDefinition({ adminConfig: { defaultLocale: 'en', - rootUrl: '/', + rootUrl: '/crud/', head: { brandName: 'Compotes', appName: '' @@ -15,13 +16,15 @@ export const dashboard = new DashboardDefinition({ sideMenu: [ new UrlAction('Homepage', '/', Home), new UrlAction('Analytics', '/analytics'), - new UrlAction('Operations', '/operations'), - new UrlAction('Tag rules', '/tag-rules'), - new UrlAction('Tags', '/tags'), - new UrlAction('Triage', '/triage'), - new UrlAction('Bank accounts', '/bank-accounts'), + new UrlAction('Operations', '/crud/operations/list'), + new UrlAction('Tag rules', '/crud/tag-rules/list'), + new UrlAction('Tags', '/crud/tags/list'), + new UrlAction('Triage', '/crud/triage/list'), + new UrlAction('Bank accounts', '/crud/bank-accounts/list'), new UrlAction('Import', '/import'), ], - cruds: [] + cruds: [ + OperationCrud, + ] }); diff --git a/src/lib/crud/cruds/OperationCrud.ts b/src/lib/crud/cruds/OperationCrud.ts new file mode 100644 index 00000000..2d72ff13 --- /dev/null +++ b/src/lib/crud/cruds/OperationCrud.ts @@ -0,0 +1,76 @@ +import { + CallbackStateProcessor, + CallbackStateProvider, CheckboxField, + CrudDefinition, DateField, + List, + NumberField, + PaginatedResults, + TextField, + UrlAction, + View +} from "@orbitale/svelte-admin"; +import type {CrudOperation} from "@orbitale/svelte-admin/dist/Crud/Operations"; +import type {RequestParameters} from "@orbitale/svelte-admin/dist/request"; +import {getOperationById, getOperations, getOperationsCount} from "$lib/db/operations"; +import type Operation from "$lib/entities/Operation"; + +export default new CrudDefinition('operations', { + defaultOperationName: "list", + label: {plural: "Operations", singular: "Operation"}, + // minStateLoadingTimeMs: 0, + + operations: [ + new List( + [ + new DateField('operation_date', 'Date'), + new TextField('op_type', 'Type 1'), + new TextField('type_display', 'Type 2'), + new TextField('details', 'Details'), + new TextField('tags', 'Tags'), + new NumberField('amount', 'Montant'), + ], + [ + new UrlAction('View', '/crud/operations/view'), + ], + { + pagination: { + enabled: true, + itemsPerPage: 20, + } + }), + new View([ + new TextField('id', 'ID'), + new DateField('operation_date', 'Date'), + new TextField('op_type', 'Type 1'), + new TextField('type_display', 'Type 2'), + new TextField('details', 'Details'), + new NumberField('amount_in_cents', 'Montant (in cents)'), + new NumberField('amount', 'Montant'), + new NumberField('hash', 'Hash'), + new TextField('state', 'State'), + new TextField('bank_account', 'Bank account'), + new TextField('tags', 'Tags'), + new CheckboxField('ignored_from_charts', 'Is ignored from charts'), + ]), + ], + + stateProvider: new CallbackStateProvider(async (operation: CrudOperation, requestParameters: RequestParameters) => { + if (typeof window === 'undefined') { + // SSR, can't call Tauri API then. + return Promise.resolve([]); + } + + if (operation.name === 'list') { + const results = await getOperations(requestParameters.page||1); + const numberOfItems = await getOperationsCount(null); + return Promise.resolve(new PaginatedResults(requestParameters.page, numberOfItems / operation.options.pagination.itemsPerPage, numberOfItems, results)); + } + + if (operation.name === 'view' || operation.name === 'edit') { + return getOperationById(requestParameters.id); + } + + return Promise.resolve(null); + }), + stateProcessor: new CallbackStateProcessor(() => {}) +}); diff --git a/src/lib/db/operations.ts b/src/lib/db/operations.ts index ffa35537..bf75fcc3 100644 --- a/src/lib/db/operations.ts +++ b/src/lib/db/operations.ts @@ -36,7 +36,7 @@ export async function getOperations( sortableField: SortableField | null = null, filters: Array | null = null ): Promise> { - const params = { page }; + const params: {[key: string]: number|string|Array} = { page }; if (sortableField) { params['orderField'] = sortableField.property_name; @@ -160,6 +160,11 @@ async function normalizeOperationFromDeserialized( deserialized_operation: DeserializedOperation ): Promise { const bank_account = await getBankAccountById(deserialized_operation.bank_account_id); + + if (!bank_account) { + throw new Error(`Backend could not find bank account with id "${deserialized_operation.bank_account_id}".`); + } + const tags = await getTagsByIds(deserialized_operation.tags_ids); return new Operation( diff --git a/src/lib/utils/configure_app.ts b/src/lib/utils/configure_app.ts deleted file mode 100644 index bb85128b..00000000 --- a/src/lib/utils/configure_app.ts +++ /dev/null @@ -1,116 +0,0 @@ -import OperationsSynchronizer from '$lib/struct/OperationsSynchronizer'; -import { getOperations, getTriageOperations } from '$lib/db/operations'; -import { updateConfig as updateAdminConfig } from '$lib/admin/src/config'; -import { getBankAccounts } from '$lib/db/bank_accounts'; -import SavedFilter from '$lib/admin/src/SavedFilter'; -import FilterWithValue from '$lib/admin/src/FilterWithValue'; -import FilterType from '$lib/admin/src/FilterType'; -import { DateTime } from 'luxon'; -import { getTags } from '$lib/db/tags'; -import { getTagRules } from '$lib/db/tag_rules'; - -const DATE_FORMAT = 'yyyy-MM-dd'; -const now = DateTime.now(); -const first_day_of_this_year = now.set({ months: 1, days: 1 }); -const last_day_of_this_year = now.set({ months: 12, days: 31 }); -const first_day_of_last_year = first_day_of_this_year.minus({ years: 1 }); -const last_day_of_last_year = last_day_of_this_year.minus({ years: 1 }); - -function splash_message(message): HTMLElement { - console.info(`Configuration log: "${message}".`); - let container = document.getElementById('splash_messages'); - if (!container) { - console.warn('Cannot add splash message when container is not present in the DOM.'); - return; - } - let textElement = document.createElement('div'); - textElement.style.opacity = '1'; - textElement.style.transition = 'opacity 2s linear 1s'; - textElement.innerText = message; - - let loaderElement = document.createElement('span'); - loaderElement.innerText = ' 🌘'; - loaderElement.style.marginLeft = '3px'; - loaderElement.style.display = 'inline-block'; - loaderElement.style.position = 'relative'; - loaderElement.classList.add('animate-rotation'); - loaderElement.addEventListener('message_end', () => { - loaderElement.classList.remove('animate-rotation'); - loaderElement.innerText = ' 🌝'; - textElement.style.opacity = '0'; - }); - textElement.appendChild(loaderElement); - - container.appendChild(textElement); - - return loaderElement; -} - -function disable_message(element: HTMLElement) { - if (!element) { - console.warn('Cannot disable message for inexistent element.'); - return; - } - element.dispatchEvent(new Event('message_end')); -} - -export default async function configure() { - let e; - - console.info('Configuring...'); - - e = splash_message('Configuring administration panel'); - updateAdminConfig({ - spinLoaderSrc: '/logo.svg', - builtinFilters: { - operations: [ - new SavedFilter('', []), - new SavedFilter('', [ - new FilterWithValue( - 'operation_date', - FilterType.date, - first_day_of_this_year.toFormat(DATE_FORMAT) + - ';' + - last_day_of_this_year.toFormat(DATE_FORMAT) - ) - ]), - new SavedFilter('', [ - new FilterWithValue( - 'operation_date', - FilterType.date, - first_day_of_last_year.toFormat(DATE_FORMAT) + - ';' + - last_day_of_last_year.toFormat(DATE_FORMAT) - ) - ]) - ] - } - }); - disable_message(e); - - // Preload everything - e = splash_message('Configuring synchronization'); - OperationsSynchronizer.clearSyncCallbacks(); - OperationsSynchronizer.addAfterSyncCallback(getOperations); - OperationsSynchronizer.addAfterSyncCallback(getTriageOperations); - OperationsSynchronizer.addAfterSyncCallback(getBankAccounts); - OperationsSynchronizer.addAfterSyncCallback(getTags); - OperationsSynchronizer.addAfterSyncCallback(getTagRules); - disable_message(e); - - let splash_enabled = document.getElementById('splash_enabled'); - if (splash_enabled) splash_enabled.classList.remove('splash_enabled'); - - document.getElementById('app').style.display = ''; - - let splash_screen = document.getElementById('splash_screen'); - if (splash_screen) { - splash_screen.style.opacity = '0'; - - setTimeout(function () { - splash_screen.style.display = 'none'; - splash_screen.style.visibility = 'hidden'; - }, 500); - } - console.info('Finished configuring ✔'); -} diff --git a/src/routes/(main)/+layout.svelte b/src/routes/(main)/+layout.svelte index c02956f0..8d97993f 100644 --- a/src/routes/(main)/+layout.svelte +++ b/src/routes/(main)/+layout.svelte @@ -1,24 +1,5 @@ diff --git a/src/routes/(main)/+page.svelte b/src/routes/(main)/+page.svelte index 6835b425..90f7c264 100644 --- a/src/routes/(main)/+page.svelte +++ b/src/routes/(main)/+page.svelte @@ -8,7 +8,7 @@
    {#each dashboard.cruds as crud}
  • - + {crud.options.label.plural}
  • diff --git a/src/routes/(svelteadmin)/[crud]/[operation]/+page.server.ts b/src/routes/(svelteadmin)/crud/[crud]/[operation]/+page.server.ts similarity index 100% rename from src/routes/(svelteadmin)/[crud]/[operation]/+page.server.ts rename to src/routes/(svelteadmin)/crud/[crud]/[operation]/+page.server.ts diff --git a/src/routes/(svelteadmin)/[crud]/[operation]/+page.svelte b/src/routes/(svelteadmin)/crud/[crud]/[operation]/+page.svelte similarity index 84% rename from src/routes/(svelteadmin)/[crud]/[operation]/+page.svelte rename to src/routes/(svelteadmin)/crud/[crud]/[operation]/+page.svelte index 4b953a08..6472f498 100644 --- a/src/routes/(svelteadmin)/[crud]/[operation]/+page.svelte +++ b/src/routes/(svelteadmin)/crud/[crud]/[operation]/+page.svelte @@ -10,6 +10,8 @@ $: requestParameters = getRequestParams($page, browser); +
    URL: {$page.url}
    + {#key $page}