create results page and routes
This commit is contained in:
parent
e7f70b9295
commit
60bdd909a7
16
.bruno/BRF/Financial Years.bru
Normal file
16
.bruno/BRF/Financial Years.bru
Normal file
@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Financial Years
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/api/financial-years
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
.bruno/BRF/Result.bru
Normal file
16
.bruno/BRF/Result.bru
Normal file
@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Result
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/api/results/2018
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
.bruno/BRF/Results.bru
Normal file
16
.bruno/BRF/Results.bru
Normal file
@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Results
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{base_url}}/api/results
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
15
.bruno/BRF/bruno.json
Normal file
15
.bruno/BRF/bruno.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "BRF",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
],
|
||||
"size": 0,
|
||||
"filesCount": 0,
|
||||
"presets": {
|
||||
"requestType": "http",
|
||||
"requestUrl": "{{base_url}}/"
|
||||
}
|
||||
}
|
||||
3
.bruno/BRF/collection.bru
Normal file
3
.bruno/BRF/collection.bru
Normal file
@ -0,0 +1,3 @@
|
||||
vars:pre-request {
|
||||
base_url: https://brf.local
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import { h } from 'preact'
|
||||
import Head from './head.ts'
|
||||
|
||||
const OtherPage = () => (
|
||||
<section>
|
||||
<Head>
|
||||
<title> : Other</title>
|
||||
</Head>
|
||||
|
||||
<h1>Other Page</h1>
|
||||
<p>Not the page where it begins</p>
|
||||
</section>
|
||||
)
|
||||
|
||||
export default OtherPage
|
||||
31
client/public/components/result.tsx
Normal file
31
client/public/components/result.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { h, type FunctionalComponent } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
|
||||
interface Props {
|
||||
year: number
|
||||
}
|
||||
|
||||
const Result: FunctionalComponent<Props> = ({ year }) => {
|
||||
const [result, setResults] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
rek(`/api/results/${year}`).then(setResults)
|
||||
}, [year])
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tbody>
|
||||
{result.map((result) => (
|
||||
<tr>
|
||||
<td>{result.accountNumber}</td>
|
||||
<td>{result.description}</td>
|
||||
<td>{result.amount}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
export default Result
|
||||
5
client/public/components/results_page.module.scss
Normal file
5
client/public/components/results_page.module.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.years {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: start;
|
||||
}
|
||||
41
client/public/components/results_page.tsx
Normal file
41
client/public/components/results_page.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { h } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import rek from 'rek'
|
||||
import Head from './head.ts'
|
||||
import Result from './result.tsx'
|
||||
import s from './results_page.module.scss'
|
||||
|
||||
const ResultsPage = () => {
|
||||
const [financialYears, setFinancialYears] = useState([])
|
||||
const [currentYear, setCurrentYear] = useState<number>(null)
|
||||
|
||||
useEffect(() => {
|
||||
rek('/api/financial-years').then((financialYears) => {
|
||||
setFinancialYears(financialYears)
|
||||
setCurrentYear(financialYears[financialYears.length - 1].year)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Head>
|
||||
<title> : Results</title>
|
||||
</Head>
|
||||
|
||||
<h1>Results</h1>
|
||||
<div className={s.years}>
|
||||
{financialYears.map((financialYear) => (
|
||||
<button onClick={() => setCurrentYear(financialYear.year)}>{financialYear.year}</button>
|
||||
))}
|
||||
</div>
|
||||
{currentYear ? (
|
||||
<div>
|
||||
<h2>{currentYear}</h2>
|
||||
<Result year={currentYear} />
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default ResultsPage
|
||||
@ -1,5 +1,5 @@
|
||||
import Start from './components/start_page.tsx'
|
||||
import Other from './components/other_page.tsx'
|
||||
import Results from './components/results_page.tsx'
|
||||
|
||||
export default [
|
||||
{
|
||||
@ -9,9 +9,9 @@ export default [
|
||||
component: Start,
|
||||
},
|
||||
{
|
||||
path: '/other',
|
||||
name: 'other',
|
||||
title: 'Other',
|
||||
component: Other,
|
||||
path: '/results',
|
||||
name: 'results',
|
||||
title: 'Results',
|
||||
component: Results,
|
||||
},
|
||||
]
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
\restrict EQkcX1mt4Oqej8UZL2Z1qyvnzIAf3O4TYefngsqIN91Lr6JLNTawzD4OIRSJr2s
|
||||
\restrict H2xBPQq6I6IQHZkAgiuKoY9lao4r4KAPtiyNvDE9oc0DN75cJ1gNbkoGFqutWDp
|
||||
|
||||
-- Dumped from database version 18.1
|
||||
-- Dumped by pg_dump version 18.1
|
||||
@ -162,6 +162,7 @@ ALTER SEQUENCE public.entry_id_seq OWNED BY public.entry.id;
|
||||
|
||||
CREATE TABLE public.financial_year (
|
||||
id integer NOT NULL,
|
||||
year integer NOT NULL,
|
||||
start_date date NOT NULL,
|
||||
end_date date NOT NULL
|
||||
);
|
||||
@ -402,6 +403,14 @@ ALTER TABLE ONLY public.financial_year
|
||||
ADD CONSTRAINT financial_year_start_date_end_date_key UNIQUE (start_date, end_date);
|
||||
|
||||
|
||||
--
|
||||
-- Name: financial_year financial_year_year_key; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.financial_year
|
||||
ADD CONSTRAINT financial_year_year_key UNIQUE (year);
|
||||
|
||||
|
||||
--
|
||||
-- Name: journal journal_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
@ -486,5 +495,5 @@ ALTER TABLE ONLY public.transactions_to_objects
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
\unrestrict EQkcX1mt4Oqej8UZL2Z1qyvnzIAf3O4TYefngsqIN91Lr6JLNTawzD4OIRSJr2s
|
||||
\unrestrict H2xBPQq6I6IQHZkAgiuKoY9lao4r4KAPtiyNvDE9oc0DN75cJ1gNbkoGFqutWDp
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
"@bmp/highlight-stack": "^0.1.2",
|
||||
"@fastify/middie": "^9.0.3",
|
||||
"@fastify/static": "^8.3.0",
|
||||
"@fastify/type-provider-typebox": "^6.1.0",
|
||||
"chalk": "^5.6.2",
|
||||
"fastify": "^5.6.2",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
@ -37,7 +38,8 @@
|
||||
"pg-protocol": "^1.10.3",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"preact": "^10.27.2",
|
||||
"preact-router": "^4.1.2"
|
||||
"preact-router": "^4.1.2",
|
||||
"rek": "^0.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
@ -53,6 +55,7 @@
|
||||
"oxlint": "^1.29.0",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.85.1",
|
||||
"typebox": "^1.0.55",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
|
||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@ -20,6 +20,9 @@ importers:
|
||||
'@fastify/static':
|
||||
specifier: ^8.3.0
|
||||
version: 8.3.0
|
||||
'@fastify/type-provider-typebox':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(typebox@1.0.55)
|
||||
chalk:
|
||||
specifier: ^5.6.2
|
||||
version: 5.6.2
|
||||
@ -53,6 +56,9 @@ importers:
|
||||
preact-router:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(preact@10.27.2)
|
||||
rek:
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
specifier: ^7.26.10
|
||||
@ -93,6 +99,9 @@ importers:
|
||||
sass:
|
||||
specifier: ^1.85.1
|
||||
version: 1.94.2
|
||||
typebox:
|
||||
specifier: ^1.0.55
|
||||
version: 1.0.55
|
||||
typescript:
|
||||
specifier: ^5.8.2
|
||||
version: 5.9.3
|
||||
@ -591,6 +600,11 @@ packages:
|
||||
'@fastify/static@8.3.0':
|
||||
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
|
||||
|
||||
'@fastify/type-provider-typebox@6.1.0':
|
||||
resolution: {integrity: sha512-k29cOitDRcZhMXVjtRq0+caKxdWoArz7su+dQWGzGWnFG+fSKhevgiZ7nexHWuXOEEQzgJlh6cptIMu69beaTA==}
|
||||
peerDependencies:
|
||||
typebox: ^1.0.13
|
||||
|
||||
'@isaacs/balanced-match@4.0.1':
|
||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
@ -1844,6 +1858,9 @@ packages:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
rek@0.8.1:
|
||||
resolution: {integrity: sha512-DXklCeA33/W9oaKjn/Upiwpdt7ZYqGJE2+1/l5v9iXwRzlfTEDtG7wMJLSWXftXgQz7/IlOjmLgOY/5OwbFPOA==}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2076,6 +2093,9 @@ packages:
|
||||
resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
typebox@1.0.55:
|
||||
resolution: {integrity: sha512-TP02wN0B6tDZngprrGVu/Z9s/QUyVEmR7VIg1yEOtsqyDdXXEoQPSfWdkD2PsA2lGLxu6GgwOTtGZVS9CAoERg==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
@ -2604,6 +2624,10 @@ snapshots:
|
||||
fastq: 1.19.1
|
||||
glob: 11.1.0
|
||||
|
||||
'@fastify/type-provider-typebox@6.1.0(typebox@1.0.55)':
|
||||
dependencies:
|
||||
typebox: 1.0.55
|
||||
|
||||
'@isaacs/balanced-match@4.0.1': {}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
@ -3833,6 +3857,8 @@ snapshots:
|
||||
gopd: 1.2.0
|
||||
set-function-name: 2.0.2
|
||||
|
||||
rek@0.8.1: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
@ -4080,6 +4106,8 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
typebox@1.0.55: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
@ -158,7 +158,11 @@ export default async function parseStream(stream: ReadableStream, decoder: Decod
|
||||
|
||||
if (yearNumber !== 0) continue
|
||||
|
||||
currentYear = (await trx('financial_year').insert({ startDate, endDate }).returning('*'))[0]
|
||||
currentYear = (
|
||||
await trx('financial_year')
|
||||
.insert({ year: startDate.slice(0, 4), startDate, endDate })
|
||||
.returning('*')
|
||||
)[0]
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@ -1,59 +1,85 @@
|
||||
import { type FastifyPluginCallback } from 'fastify'
|
||||
import { Type, type Static, type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox'
|
||||
import knex from '../lib/knex.ts'
|
||||
|
||||
const apiRoutes: FastifyPluginCallback = (fastify, _, done) => {
|
||||
export const FinancialYear = Type.Object({
|
||||
year: Type.Number(),
|
||||
startDate: Type.String(),
|
||||
endDate: Type.String(),
|
||||
})
|
||||
|
||||
export type FinancialYearType = Static<typeof FinancialYear>
|
||||
|
||||
const apiRoutes: FastifyPluginCallbackTypebox = (fastify, _, done) => {
|
||||
fastify.route({
|
||||
url: '/result',
|
||||
url: '/financial-years',
|
||||
method: 'GET',
|
||||
async handler(req, res) {
|
||||
handler() {
|
||||
return knex('financialYear').select('*')
|
||||
},
|
||||
})
|
||||
|
||||
fastify.route({
|
||||
url: '/results',
|
||||
method: 'GET',
|
||||
async handler() {
|
||||
const years = await knex('financialYear').select('*')
|
||||
|
||||
const accounts = await knex('account').select('*')
|
||||
|
||||
return Promise.all(
|
||||
years.map((year) =>
|
||||
knex('account')
|
||||
.select('account.number', 'account.description')
|
||||
.sum('transaction.amount as amount')
|
||||
.innerJoin('transaction', function () {
|
||||
this.on('transaction.accountNumber', '=', 'account.number')
|
||||
knex('account AS a')
|
||||
.select('a.number', 'a.description')
|
||||
.sum('t.amount as amount')
|
||||
.innerJoin('transaction AS t', function () {
|
||||
this.on('t.accountNumber', '=', 'a.number')
|
||||
})
|
||||
.innerJoin('entry', function () {
|
||||
this.on('transaction.entryId', '=', 'entry.id')
|
||||
.innerJoin('entry AS e', function () {
|
||||
this.on('t.entryId', '=', 'e.id')
|
||||
})
|
||||
.groupBy('account.number', 'account.description')
|
||||
.where('account.number', '>=', 3000)
|
||||
.where('entry.financialYearId', year.id)
|
||||
.orderBy('account.number')
|
||||
.groupBy('a.number', 'a.description')
|
||||
.where('a.number', '>=', 3000)
|
||||
.where('e.financialYearId', year.id)
|
||||
.orderBy('a.number')
|
||||
.then((result) => ({
|
||||
startDate: year.startDate,
|
||||
endDate: year.endDate,
|
||||
result,
|
||||
})),
|
||||
),
|
||||
)
|
||||
).then((years) => ({
|
||||
accounts,
|
||||
years,
|
||||
}))
|
||||
},
|
||||
})
|
||||
|
||||
fastify.route({
|
||||
url: '/result/:year',
|
||||
url: '/results/:year',
|
||||
method: 'GET',
|
||||
async handler(req, res) {
|
||||
const year = await knex('financialYear').first('*').where('startDate', `${req.params.year}0101`)
|
||||
schema: {
|
||||
params: Type.Object({
|
||||
year: Type.Number(),
|
||||
}),
|
||||
},
|
||||
async handler(req) {
|
||||
const year = await knex('financialYear').first('*').where('year', req.params.year)
|
||||
|
||||
const result = await knex('account')
|
||||
.select('account.number', 'account.description')
|
||||
.sum('transaction.amount as amount')
|
||||
.innerJoin('transaction', function () {
|
||||
this.on('transaction.accountNumber', '=', 'account.number')
|
||||
})
|
||||
.innerJoin('entry', function () {
|
||||
this.on('transaction.entryId', '=', 'entry.id')
|
||||
})
|
||||
.groupBy('account.number', 'account.description')
|
||||
.where('account.number', '>=', 3000)
|
||||
.where('entry.financialYearId', year.id)
|
||||
.orderBy('account.number')
|
||||
if (!year) return null
|
||||
|
||||
return result
|
||||
return knex('transaction AS t')
|
||||
.select('t.accountNumber', 'a.description')
|
||||
.sum('t.amount as amount')
|
||||
.innerJoin('account AS a', function () {
|
||||
this.on('t.accountNumber', '=', 'a.number')
|
||||
})
|
||||
.innerJoin('entry AS e', function () {
|
||||
this.on('t.entryId', '=', 'e.id')
|
||||
})
|
||||
.groupBy('t.accountNumber', 'a.description')
|
||||
.where('t.accountNumber', '>=', 3000)
|
||||
.where('e.financialYearId', year.id)
|
||||
.orderBy('t.accountNumber')
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import fastify from 'fastify'
|
||||
import fastify, { type FastifyServerOptions } from 'fastify'
|
||||
import StatusError from './lib/status_error.ts'
|
||||
import env from './env.ts'
|
||||
import ErrorHandler from './handlers/error.ts'
|
||||
@ -6,7 +6,7 @@ import vitePlugin from './plugins/vite.ts'
|
||||
import apiRoutes from './routes/api.ts'
|
||||
import templatePublic from './templates/public.ts'
|
||||
|
||||
export default async (options) => {
|
||||
export default async (options: FastifyServerOptions) => {
|
||||
const server = fastify(options)
|
||||
|
||||
server.setNotFoundHandler(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user