Skip to content

Commit

Permalink
feat: Cashflow chart links to transactions filtered by month (#142)
Browse files Browse the repository at this point in the history
* feat: Cashflow chart links to transactions filtered by month

* test: filter transactions by date range

* fix: add label to date range
  • Loading branch information
fmaclen authored Oct 29, 2022
1 parent 54af16d commit e1d9497
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 55 deletions.
2 changes: 1 addition & 1 deletion sveltekit/src/lib/components/ChartBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
let barBackground = isCurrentPeriod ? `--background-url: url(${currentPeriodBackground});` : '';
</script>

<div class="chart__barContainer" style={barGridTemplateRows} title={value.toString()}>
<div class="chart__barContainer" style={barGridTemplateRows}>
{#if trend === 'negative'}
<div class="chart__barPlaceholder" />
<hr class="chart__hr" />
Expand Down
22 changes: 15 additions & 7 deletions sveltekit/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { format, fromUnixTime } from 'date-fns';
import { endOfMonth, format, fromUnixTime } from 'date-fns';
import type { PageData } from './$types';
import ScrollView from '$lib/components/ScrollView.svelte';
Expand Down Expand Up @@ -84,11 +84,18 @@
[chart.highestSurplus, chart.lowestSurplus].includes(period.surplus)}
{@const month = fromUnixTime(period.month)}
{@const isJanuary = month.getMonth() === 0}

<div
class="chart__period {isActive && 'chart__period--active'} {isJanuary &&
'chart__period--january'}"
<a
on:mouseenter={() => setActivePeriod(period)}
href="/transactions
?periodFrom={format(month, 'yyyy-MM-dd')}
&periodTo={format(endOfMonth(month), 'yyyy-MM-dd')}
&periodLabel={format(month, 'MMMM yyyy')}
"
title="See transactions in {format(month, 'MMMM yyyy')}"
class="chart__period
{isActive && 'chart__period--active'}
{isJanuary && 'chart__period--january'}
"
>
<ChartBar
{isCurrentPeriod}
Expand All @@ -104,7 +111,7 @@
{format(month, 'MMM')}
{isJanuary ? `'${format(month, 'yy')}` : ''}
</time>
</div>
</a>
{/each}
</div>
<div class="bigPictureCashflow__summary">
Expand Down Expand Up @@ -180,11 +187,12 @@
grid-auto-columns: minmax(0, 1fr);
}
div.chart__period {
a.chart__period {
display: flex;
flex-direction: column;
border-left: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
text-decoration: none;
&:first-child {
border-left: none;
Expand Down
94 changes: 59 additions & 35 deletions sveltekit/src/routes/transactions/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,42 @@
const title = 'Transactions';
enum Filter {
ALL = 'All',
CREDITS = 'Credits',
DEBITS = 'Debits'
}
const TABLE_HEADERS = [
{
label: 'Date',
column: 'date'
},
{
label: 'Description',
column: 'description'
},
{
label: 'Category',
column: 'categoryId'
},
{
label: 'Account',
column: 'accountId'
},
{
label: 'Amount',
column: 'value'
}
];
// Filter transactions by date range
const today = new Date();
const thisMonthFrom = startOfMonth(today);
const thisMonthTo = endOfMonth(today);
const thisYearFrom = startOfYear(today);
const thisYearTo = endOfYear(today);
const periods = [
let periods = [
{
label: 'This month',
dateFrom: thisMonthFrom,
Expand Down Expand Up @@ -80,48 +110,20 @@
}
];
enum Filter {
ALL = 'All',
CREDITS = 'Credits',
DEBITS = 'Debits'
}
const TABLE_HEADERS = [
{
label: 'Date',
column: 'date'
},
{
label: 'Description',
column: 'description'
},
{
label: 'Category',
column: 'categoryId'
},
{
label: 'Account',
column: 'accountId'
},
{
label: 'Amount',
column: 'value'
}
];
// Default params
$: transactions = [] as TransactionResponse[];
$: filteredTransactions = [] as TransactionResponse[];
$: filterBy = Filter.ALL;
$: periodIndex = 2; // Last 3 months
$: dateFrom = format(dateInUTC(periods[periodIndex].dateFrom), 'yyyy-MM-dd');
$: dateTo = format(dateInUTC(periods[periodIndex].dateTo), 'yyyy-MM-dd');
// Transaction sorting and filtering
$: sortBy = TABLE_HEADERS[0].column; // Date column
$: sortOrder = SortOrder.DESC;
$: keyword = '';
$: periodIndex = 2; // Last 3 months
$: dateFrom = format(dateInUTC(periods[periodIndex].dateFrom), 'yyyy-MM-dd');
$: dateTo = format(dateInUTC(periods[periodIndex].dateTo), 'yyyy-MM-dd');
const getTransactions = async () => {
const params = [
`dateFrom=${dateFrom}`,
Expand All @@ -136,8 +138,30 @@
setFilterBy(filterBy);
};
// When the component is mounted retrieve transactions with the default params
// When the component is mounted retrieve transactions with the params set in
// the URL or with default params if no params are present.
onMount(async () => {
const urlParams = new URLSearchParams(window.location.search);
const periodLabel = urlParams.get('periodLabel');
const periodFrom = urlParams.get('periodFrom');
const periodTo = urlParams.get('periodTo');
// Add a new period with the custom date range
if (periodFrom && periodTo && periodLabel) {
periods = [
...periods,
{
label: periodLabel,
dateFrom: dateInUTC(new Date(periodFrom)),
dateTo: dateInUTC(new Date(periodTo))
}
];
periodIndex = periods.length - 1;
dateFrom = periodFrom;
dateTo = periodTo;
}
await getTransactions();
});
Expand Down
14 changes: 3 additions & 11 deletions sveltekit/tests/theBigPicture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,9 @@ test.describe('Balance sheet', () => {

test('Cashflow totals are calculated and rendered in a chart correctly', async ({ page }) => {
await page.goto('/');
expect(
await page.locator('.bigPictureCashflow__summary .card', { hasText: 'Income' }).textContent()
).toMatch('$0');
expect(
await page
.locator('.bigPictureCashflow__summary .card', { hasText: 'Expenses' })
.textContent()
).toMatch('$0');
expect(
await page.locator('.bigPictureCashflow__summary .card', { hasText: 'Surplus' }).textContent()
).toMatch('$0');
expect(await page.locator('.bigPictureCashflow__summary .card', { hasText: 'Income' }).textContent()).toMatch('$0'); // prettier-ignore
expect(await page.locator('.bigPictureCashflow__summary .card', { hasText: 'Expenses' }).textContent()).toMatch('$0'); // prettier-ignore
expect(await page.locator('.bigPictureCashflow__summary .card', { hasText: 'Surplus' }).textContent()).toMatch('$0'); // prettier-ignore

const chartPeriods = page.locator('.chart__period');
expect(await chartPeriods.count()).toBe(13);
Expand Down
35 changes: 34 additions & 1 deletion sveltekit/tests/transactions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test } from '@playwright/test';
import { format, addDays, startOfMonth } from 'date-fns';
import { format, addDays, startOfMonth, subMonths } from 'date-fns';

import { databaseSeed, databaseWipe, delay } from './fixtures/helpers.js';

Expand Down Expand Up @@ -841,4 +841,37 @@ test.describe('Transactions', () => {
expect(await tableRows.nth(1).textContent()).toMatch('15');
});
});

test('Filter transactions by a custom date range', async ({ page, baseURL }) => {
await databaseSeed(baseURL!);

await page.goto('/');
await expect(page.locator('h1', { hasText: 'The big picture' })).toBeVisible();

const chartPeriods = page.locator('.chart__period');
expect(await chartPeriods.count()).toBe(13);

await chartPeriods.nth(0).click();
await expect(page.locator('h1', { hasText: 'Transactions' })).toBeVisible();

const selectOptions = page.locator('.formSelect__select option');
const twelveMonthsAgo = format(subMonths(new Date(), 12), 'MMMM yyyy');
const sixMonthsAgo = format(subMonths(new Date(), 7), 'MMMM yyyy');
const thisMonth = format(new Date(), 'MMMM yyyy');
expect(await selectOptions.nth(8).textContent()).toMatch(twelveMonthsAgo);
expect(await page.locator('.card', { hasText: 'Transactions' }).textContent()).toMatch('37');
expect(await page.locator('.card', { hasText: 'Net balance' }).textContent()).toMatch('$808.77'); // prettier-ignore

await page.locator('a', { hasText: 'The big picture' }).click();
await chartPeriods.nth(5).click();
expect(await selectOptions.nth(8).textContent()).toMatch(sixMonthsAgo);
expect(await page.locator('.card', { hasText: 'Transactions' }).textContent()).toMatch('37');
expect(await page.locator('.card', { hasText: 'Net balance' }).textContent()).toMatch('$217.01'); // prettier-ignore

await page.locator('a', { hasText: 'The big picture' }).click();
await chartPeriods.nth(12).click();
expect(await selectOptions.nth(8).textContent()).toMatch(thisMonth);
expect(await page.locator('.card', { hasText: 'Transactions' }).textContent()).toMatch('37');
expect(await page.locator('.card', { hasText: 'Net balance' }).textContent()).toMatch('$228.44'); // prettier-ignore
});
});

0 comments on commit e1d9497

Please sign in to comment.