Skip to content

Commit

Permalink
feat: add transactions batch-editor (#126)
Browse files Browse the repository at this point in the history
* feat: add transactions batch-editor

Fixes #125

* validate slug is a number

* add DangerZone and Discard button to batch editor

* add transactions multi-select

* chore: update svelte-currency-input

* fix tests

* remove existing highlight

* add ability to delete multiple transactions

* remove error props from Account and Asset forms

* add ability to edit multiple transactions

* increase hit area of checkboxes

* Danger zone: camelCase class name

* add batch-edit tests

* add test assertion and update comments

* remove redundant test steps
  • Loading branch information
fmaclen authored Oct 18, 2022
1 parent 9e2cf56 commit 4703228
Show file tree
Hide file tree
Showing 36 changed files with 1,085 additions and 250 deletions.
14 changes: 7 additions & 7 deletions sveltekit/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"type": "module",
"dependencies": {
"@canutin/svelte-currency-input": "^0.7.1",
"@canutin/svelte-currency-input": "^0.7.2",
"@prisma/client": "^4.4.0",
"date-fns": "^2.29.2",
"date-fns-tz": "^1.3.6",
Expand Down
1 change: 1 addition & 0 deletions sveltekit/src/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
--color-white-alpha50:rgba(255,255,255,.5);

--color-black:#000;
--color-black-alpha10:rgba(0,0,0,.10);
--color-black-alpha15:rgba(0,0,0,.15);
--color-black-alpha35:rgba(0,0,0,.35);

Expand Down
12 changes: 6 additions & 6 deletions sveltekit/src/lib/components/DangerZone.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
export let handleDelete: (e: Event) => void;
</script>

<div class="danger-zone">
<div class="danger-zone__notice">
<p class="danger-zone__p">
<div class="dangerZone">
<div class="dangerZone__notice">
<p class="dangerZone__p">
<slot />
</p>
<p class="danger-zone__p danger-zone__p--negative">{UNDOABLE_ACTION.split('🚩')[1]}</p>
<p class="dangerZone__p dangerZone__p--negative">{UNDOABLE_ACTION.split('🚩')[1]}</p>
</div>
<nav>
<Button on:click={handleDelete}>Delete</Button>
</nav>
</div>

<style lang="scss">
div.danger-zone {
div.dangerZone {
display: flex;
align-items: center;
justify-content: space-between;
Expand All @@ -33,7 +33,7 @@
}
}
p.danger-zone__p {
p.dangerZone__p {
font-size: 12px;
margin: 0;
color: var(--color-grey70);
Expand Down
62 changes: 62 additions & 0 deletions sveltekit/src/lib/components/FormDateInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script lang="ts">
import FormSelect from './FormSelect.svelte';
import { LOCALE } from '$lib/helpers/misc';
export let date: Date = new Date();
export let name: string;
export let disabled: boolean = false;
$: inputDate = new Date(`${thisYear}-${thisMonth}-${thisDate}`).getTime() / 1000;
let thisYear = date.getUTCFullYear();
const years = [
// Past 15 years
...Array.from(Array(15).keys())
.map((i) => thisYear - 1 - i)
.reverse(),
thisYear,
// Next 15 years
...Array.from(Array(15).keys()).map((i) => thisYear + 1 + i)
];
let thisMonth = date.getUTCMonth() + 1;
let months = Array.from(Array(12).keys()).map((i) => i + 1); // 12 months in a year
let thisDate = date ? date.getUTCDate() : new Date().getUTCDate();
const days = Array.from(Array(31).keys()).map((i) => i + 1); // 31 days in a month
const getDateSelects = (dates: number[]) => {
const isMonth = dates.length === 12;
return dates.map((date) => {
return {
label: isMonth
? `${date} - ${new Date(thisYear, date - 1, 1).toLocaleString(LOCALE, {
month: 'short'
})}` // e.g. "9 - Sep"
: date.toString(),
value: date
};
});
};
</script>

<div class="form__date-field">
<input type="hidden" {name} value={inputDate} />
<FormSelect name="yearSelect" options={getDateSelects(years)} {disabled} bind:value={thisYear} />
<FormSelect
name="monthSelect"
options={getDateSelects(months)}
{disabled}
bind:value={thisMonth}
/>
<FormSelect name="dateSelect" options={getDateSelects(days)} {disabled} bind:value={thisDate} />
</div>

<style lang="scss">
div.form__date-field {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 4px;
}
</style>
23 changes: 23 additions & 0 deletions sveltekit/src/lib/components/FormEditableField.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import FormInputCheckbox from './FormInputCheckbox.svelte';
export let name: string;
export let label: string;
export let checked: boolean;
export let required: boolean = false;
export let disabled: boolean = false;
export let indeterminate: boolean = false;
</script>

<div class="form__editable-field">
<slot />
<FormInputCheckbox {name} {label} {required} {disabled} {indeterminate} bind:checked />
</div>

<style lang="scss">
div.form__editable-field {
display: grid;
grid-template-columns: auto max-content;
column-gap: 8px;
}
</style>
11 changes: 11 additions & 0 deletions sveltekit/src/lib/components/FormFieldFlags.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="form__field-flags">
<slot />
</div>

<style lang="scss">
div.form__field-flags {
display: flex;
flex-direction: column;
row-gap: 4px;
}
</style>
3 changes: 3 additions & 0 deletions sveltekit/src/lib/components/FormFooter.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
footer.form__footer {
display: flex;
justify-content: flex-end;
align-items: center;
font-size: 12px;
gap: 12px;
padding: 8px 12px;
background-color: var(--color-border);
}
Expand Down
3 changes: 3 additions & 0 deletions sveltekit/src/lib/components/FormInputCheckbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export let checked: boolean;
export let required: boolean = false;
export let disabled: boolean = false;
export let indeterminate: boolean = false;
</script>

<label class="formInputCheckbox {disabled && 'formInputCheckbox--disabled'}">
Expand All @@ -13,6 +14,7 @@
{name}
{required}
{disabled}
{indeterminate}
bind:checked
/>
<span class="formInputCheckbox__label">{label}</span>
Expand Down Expand Up @@ -41,5 +43,6 @@
input.formInputCheckbox__input {
margin: 0;
accent-color: var(--color-bluePrimary);
}
</style>
12 changes: 11 additions & 1 deletion sveltekit/src/lib/components/FormSelect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
export let value: number | string;
export let options: { label: string; value?: string | number }[];
export let name: string;
export let placeholder: string = '';
export let disabled: boolean = false;
</script>

<div class="formSelect">
<div class="formSelect {disabled && 'formSelect--disabled'}">
<select class="formSelect__select" type="text" {name} {disabled} bind:value on:change>
{#if placeholder}
<option value={0} disabled selected>{placeholder}</option>
{/if}
{#each options as { label, value }, i}
<option value={value || i}>{label}</option>
{/each}
Expand All @@ -17,6 +21,11 @@
div.formSelect {
position: relative;
&--disabled {
@import './Form.scss';
@include disabledInput;
}
&::after {
display: inline-block;
position: absolute;
Expand All @@ -28,6 +37,7 @@
width: 20px;
font-size: 8px;
content: '';
color: currentColor;
}
}
Expand Down
2 changes: 1 addition & 1 deletion sveltekit/src/lib/components/Link.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<style lang="scss">
a.a {
color: var(--color-grey70);
border-bottom: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-black-alpha10);
text-decoration: none;
cursor: pointer;
Expand Down
2 changes: 2 additions & 0 deletions sveltekit/src/lib/helpers/forms.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BalanceGroup, getBalanceGroupLabel } from '$lib/helpers/constants';
import prisma from '$lib/helpers/prisma';

// DEPRECATED: replace with CRUDResponse
// https://github.com/Canutin/desktop/issues/128
export interface AddOrUpdateAPIResponse {
id?: number;
error?: any;
Expand Down
5 changes: 5 additions & 0 deletions sveltekit/src/lib/helpers/misc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { redirect } from '@sveltejs/kit';
import formatInTimeZone from 'date-fns-tz/esm/formatInTimeZone';
import { SortOrder } from '$lib/helpers/constants';

Expand Down Expand Up @@ -55,3 +56,7 @@ export const api = async ({ endpoint, method, payload, params }: Api) => {
});
return await response.json();
};

export const notFound = () => {
throw redirect(307, '/404');
};
21 changes: 17 additions & 4 deletions sveltekit/src/lib/helpers/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path';
import { fork } from 'child_process';
import { env } from '$env/dynamic/private';
import { Prisma, PrismaClient } from '@prisma/client';
import { json } from '@sveltejs/kit';

const cwd = env.SVELTEKIT_PATH ? env.SVELTEKIT_PATH : process.cwd();

Expand Down Expand Up @@ -162,17 +163,29 @@ export const validateVaultSeed = async (ranTwice = false) => {
return true;
};

export const handleError = (error: any, modelName: string) => {
export const handleError = (error: any, modelName: string): CRUDResponse => {
switch (error.code) {
case 'P2002':
return { error: { name: `An ${modelName} with the same name already exists` } };
return { error: `An ${modelName} with the same name already exists` };
case 'P2025':
return { error: { name: `The ${modelName} doesn't exist` } };
return { error: `The ${modelName} doesn't exist` };
default:
return { error };
console.error(error);
return { error: 'An error ocurred and the request could not be completed' };
}
};

// DEPRECATED (...maybe)
// https://github.com/Canutin/desktop/issues/128
export interface CRUDResponse {
payload?: any; // Prisma.BatchPayload | Account | Asset | Transaction | etc...
error?: string;
}

export const crudResponse = (response: CRUDResponse): Response => {
return json(response);
};

// Default Prisma client
const prisma = new PrismaClient();

Expand Down
14 changes: 7 additions & 7 deletions sveltekit/src/routes/account.json/+server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { json } from '@sveltejs/kit';
import type { Prisma } from '@prisma/client';
import type { RequestEvent } from '@sveltejs/kit';
import prisma, { handleError } from '$lib/helpers/prisma';
import prisma, { crudResponse, handleError } from '$lib/helpers/prisma';

// Create new account
export const POST = async ({ request }: RequestEvent) => {
Expand All @@ -16,9 +16,9 @@ export const POST = async ({ request }: RequestEvent) => {
...payload
}
});
return json({ id: account.id });
return json({ id: account.id }); // FIXME: should return crudResponse
} catch (error) {
return json(handleError(error, 'account'));
return crudResponse(handleError(error, 'account'));
}
};

Expand All @@ -37,9 +37,9 @@ export const PATCH = async ({ request }: RequestEvent) => {
...payload
}
});
return json({ id: account.id });
return json({ id: account.id }); // FIXME: should return crudResponse
} catch (error) {
return json(handleError(error, 'account'));
return crudResponse(handleError(error, 'account'));
}
};

Expand All @@ -50,8 +50,8 @@ export const DELETE = async ({ request }: RequestEvent) => {
const account = await prisma.account.delete({
where: { id: payload }
});
return json({ id: account.id });
return json({ id: account.id }); // FIXME: should return crudResponse
} catch (error) {
return json(handleError(error, 'account'));
return crudResponse(handleError(error, 'account'));
}
};
5 changes: 2 additions & 3 deletions sveltekit/src/routes/account/AccountForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
export let selectBalanceGroups: FormSelectOption[];
export let lastBalanceStatement: AccountBalanceStatement | null = null;
export let submitButtonLabel: string;
export let error: any | null = null;
let name = account ? account.name : '';
let accountTypeId = account ? account.accountTypeId : 1;
Expand All @@ -29,7 +28,7 @@
<Form on:submit={handleSubmit}>
<FormFieldset>
<FormField name="name" label="Name">
<FormInput type="text" name="name" bind:value={name} error={error?.name} />
<FormInput type="text" name="name" bind:value={name} />
</FormField>
<FormField name="accountTypeId" label="Account type">
<FormSelect name="accountTypeId" options={selectAccountTypes} bind:value={accountTypeId} />
Expand Down Expand Up @@ -76,6 +75,6 @@
div.accountBalanceField {
display: grid;
grid-template-columns: minmax(96px, 1fr) 2fr;
grid-gap: 8px;
column-gap: 8px;
}
</style>
Loading

0 comments on commit 4703228

Please sign in to comment.