Compare commits
10 Commits
c8a8b8df1b
...
f9ef3e8248
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9ef3e8248 | ||
|
|
a0eb060257 | ||
|
|
e5025d89a6 | ||
|
|
6507ac7f0c | ||
|
|
259bb23ec9 | ||
|
|
2f53620de7 | ||
|
|
b13c69b46e | ||
|
|
2caf688dfa | ||
|
|
915131db58 | ||
|
|
31404b874a |
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "build"]
|
||||
243
package-lock.json
generated
243
package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"pocketbase": "^0.26.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.4.0",
|
||||
"@sveltejs/kit": "^2.47.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
@@ -21,6 +21,7 @@
|
||||
"svelte": "^5.41.0",
|
||||
"svelte-check": "^4.3.3",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tailwindcss-motion": "^1.1.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.10"
|
||||
}
|
||||
@@ -524,6 +525,112 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs": {
|
||||
"version": "28.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.9.tgz",
|
||||
"integrity": "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"commondir": "^1.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"fdir": "^6.2.0",
|
||||
"is-reference": "1.2.1",
|
||||
"magic-string": "^0.30.3",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0 || 14 >= 14.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.68.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs/node_modules/is-reference": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-json": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
|
||||
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
|
||||
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
|
||||
@@ -849,14 +956,20 @@
|
||||
"acorn": "^8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/adapter-auto": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.0.tgz",
|
||||
"integrity": "sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==",
|
||||
"node_modules/@sveltejs/adapter-node": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.4.0.tgz",
|
||||
"integrity": "sha512-NMsrwGVPEn+J73zH83Uhss/hYYZN6zT3u31R3IHAn3MiKC3h8fjmIAhLfTSOeNHr5wPYfjjMg8E+1gyFgyrEcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"rollup": "^4.9.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/kit": "^2.0.0"
|
||||
"@sveltejs/kit": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/kit": {
|
||||
@@ -1223,6 +1336,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
@@ -1282,6 +1402,13 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
@@ -1410,6 +1537,13 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
@@ -1443,6 +1577,16 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -1450,6 +1594,42 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
@@ -1804,6 +1984,13 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1979,6 +2166,27 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.53.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
|
||||
@@ -2066,6 +2274,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.43.8",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.8.tgz",
|
||||
@@ -2123,6 +2344,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss-motion": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss-motion/-/tailwindcss-motion-1.1.1.tgz",
|
||||
"integrity": "sha512-CeeQAc5o31BuEPMyWdq/786X7QWNeifa+8khfu74Fs8lGkgEwjNYv6dGv+lRFS8FWXV5dp7F3AU9JjBXjiaQfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"lint": "prettier --check ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-node": "^5.4.0",
|
||||
"@sveltejs/kit": "^2.47.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
@@ -24,6 +24,7 @@
|
||||
"svelte": "^5.41.0",
|
||||
"svelte-check": "^4.3.3",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tailwindcss-motion": "^1.1.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.10"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin 'tailwindcss-motion';
|
||||
|
||||
body {
|
||||
@apply bg-gray-50 font-sans text-gray-800 antialiased;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
const variants = {
|
||||
primary:
|
||||
'px-4 py-2 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-xl transition-all duration-200 ease-in-out disabled:opacity-70 disabled:cursor-not-allowed',
|
||||
'px-4 py-2 text-sm font-medium text-white bg-emerald-800 hover:bg-emerald-900 rounded-xl transition-all duration-200 ease-in-out disabled:opacity-70 disabled:cursor-not-allowed',
|
||||
secondary:
|
||||
'px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-100 rounded-xl transition-all duration-200 ease-in-out',
|
||||
'px-4 py-2 text-sm font-medium text-gray-100 bg-gray-600 border border-gray-300 hover:bg-gray-500 rounded-xl transition-all duration-200 ease-in-out',
|
||||
danger:
|
||||
'px-4 py-2 text-sm font-medium text-white bg-red-500 hover:bg-red-600 rounded-xl transition-all duration-200 ease-in-out disabled:opacity-70 disabled:cursor-not-allowed'
|
||||
'px-4 py-2 text-sm font-medium text-white bg-red-800 hover:bg-red-900 rounded-xl transition-all duration-200 ease-in-out disabled:opacity-70 disabled:cursor-not-allowed'
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -18,14 +19,17 @@
|
||||
href?: string;
|
||||
};
|
||||
let { children, onClick, disabled, variant = 'primary', href }: Props = $props();
|
||||
|
||||
const isActive = page.params.year === href?.split('/').pop();
|
||||
$inspect(isActive, 'isActive', page.params.year, href);
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a {href} class={variants[variant]}>
|
||||
<a {href} class={variants[variant]} class:!bg-gray-900={isActive}>
|
||||
{@render children()}
|
||||
</a>
|
||||
{:else}
|
||||
<button class={variants[variant]} onclick={onClick} {disabled}>
|
||||
<button style:cursor="pointer" class={variants[variant]} onclick={onClick} {disabled}>
|
||||
{@render children()}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="rounded-2xl bg-gray-200 p-4 shadow-sm transition-all duration-200 ease-in-out hover:shadow-md md:p-6"
|
||||
class="rounded-2xl bg-gray-500 p-4 shadow-sm transition-all duration-200 ease-in-out hover:shadow-md md:p-6"
|
||||
>
|
||||
<div class="mb-1 text-sm text-gray-600">{title}</div>
|
||||
<div class="text-3xl font-bold text-gray-800">{value}</div>
|
||||
<div class="mb-1 text-sm text-gray-200">{title}</div>
|
||||
<div class="text-3xl font-bold text-gray-100">{value}</div>
|
||||
{#if description}
|
||||
<div class="mt-2 text-xs text-gray-500">{description}</div>
|
||||
<div class="mt-2 text-xs text-gray-200">{description}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { refreshAll } from '$app/navigation';
|
||||
import { formatCurrency } from '$lib/helpers/formatCurrency';
|
||||
import { DB } from '$lib/integrations/db';
|
||||
import Badge from '../atoms/Badge.svelte';
|
||||
import Heading from '../atoms/Heading.svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import GiftModal from './GiftModal.svelte';
|
||||
|
||||
let gift: Gift = $props();
|
||||
type Props = {
|
||||
editable?: boolean;
|
||||
};
|
||||
|
||||
let gift: Gift & Props = $props();
|
||||
let isEditModal = $state(false);
|
||||
|
||||
const bgByStatus = {
|
||||
planned: 'bg-gray-600',
|
||||
@@ -17,8 +24,8 @@
|
||||
|
||||
<div
|
||||
class={`my-6 rounded-2xl p-4 shadow-sm transition-all duration-200 ease-in-out ${bgByStatus[gift.status]}`}
|
||||
in:fly={{ x: 100 }}
|
||||
out:fly={{ x: -100 }}
|
||||
class:cursor-pointer={!!gift.editable}
|
||||
onclick={() => (isEditModal = true)}
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
@@ -26,6 +33,19 @@
|
||||
<Heading size="small" spacing="none">
|
||||
{gift.title}
|
||||
</Heading>
|
||||
{#if gift.editable}
|
||||
<p
|
||||
class="cursor-pointer text-xs text-red-400"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
DB.deleteGift(gift.id).finally(() => {
|
||||
refreshAll();
|
||||
});
|
||||
}}
|
||||
>
|
||||
Usuń
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if gift.link}
|
||||
@@ -38,7 +58,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2 text-white">
|
||||
<span>Koszt: {formatCurrency(gift.cost)}</span>
|
||||
</div>
|
||||
{#if gift.description}
|
||||
@@ -46,3 +66,18 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if isEditModal}
|
||||
<GiftModal
|
||||
title="Edytuj prezent"
|
||||
personId={gift.person}
|
||||
editGift={gift}
|
||||
isOpen={isEditModal}
|
||||
onClose={() => (isEditModal = false)}
|
||||
onSave={async (data) => {
|
||||
await DB.updateGift(gift.id, data);
|
||||
refreshAll();
|
||||
isEditModal = false;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
149
src/lib/components/molecules/GiftModal.svelte
Normal file
149
src/lib/components/molecules/GiftModal.svelte
Normal file
@@ -0,0 +1,149 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import type { DB } from '$lib/integrations/db';
|
||||
import Modal from '../atoms/Modal.svelte';
|
||||
import Button from '../atoms/Button.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { formatStatus } from '$lib/helpers/formatStatus';
|
||||
|
||||
type CreateGift = Parameters<typeof DB.createGift>[0];
|
||||
|
||||
type Props = {
|
||||
onSave: (data: CreateGift) => Promise<void>;
|
||||
editGift?: Gift;
|
||||
personId: string;
|
||||
} & Omit<ComponentProps<typeof Modal>, 'children'>;
|
||||
|
||||
let { onSave, isOpen, onClose, editGift, personId }: Props = $props();
|
||||
|
||||
let gift = $state<Gift>(editGift ? { ...editGift } : ({} as Gift));
|
||||
let isLoading = $state(false);
|
||||
|
||||
if (!gift?.title) {
|
||||
gift.title = '';
|
||||
gift.description = '';
|
||||
gift.link = '';
|
||||
gift.cost = 0;
|
||||
gift.imageUrl = '';
|
||||
gift.status = 'planned';
|
||||
gift.year = page.data.year.id;
|
||||
gift.person = personId;
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
||||
isLoading = true;
|
||||
try {
|
||||
await onSave(gift);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} {onClose} title={gift ? 'Edytuj prezent' : 'Dodaj nowy prezent'}>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label for="title" class="mb-2 block text-sm font-medium text-gray-700"> Tytuł * </label>
|
||||
<input
|
||||
id="title"
|
||||
type="text"
|
||||
class="w-full rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
disabled={isLoading}
|
||||
bind:value={gift.title}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="description" class="mb-2 block text-sm font-medium text-gray-700"> Opis </label>
|
||||
<textarea
|
||||
id="description"
|
||||
rows={3}
|
||||
class="w-full resize-none rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isLoading}
|
||||
bind:value={gift.description}
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label for="cost" class="mb-2 block text-sm font-medium text-gray-700">
|
||||
Koszt (PLN) *
|
||||
</label>
|
||||
<input
|
||||
id="cost"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
bind:value={gift.cost}
|
||||
class="w-full rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="status" class="mb-2 block text-sm font-medium text-gray-700"> Status * </label>
|
||||
<select
|
||||
id="status"
|
||||
bind:value={gift.status}
|
||||
class="w-full rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
disabled={isLoading}
|
||||
>
|
||||
<option value="planned">{formatStatus('planned')} </option>
|
||||
<option value="decided">{formatStatus('decided')}</option>
|
||||
<option value="bought">{formatStatus('bought')}</option>
|
||||
<option value="ready">{formatStatus('ready')}</option>
|
||||
<option value="wrapped">{formatStatus('wrapped')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="link" class="mb-2 block text-sm font-medium text-gray-700"> Link </label>
|
||||
<textarea
|
||||
id="linki"
|
||||
bind:value={gift.link}
|
||||
rows={3}
|
||||
class="w-full resize-none rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isLoading}
|
||||
></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="ceneoId" class="mb-2 block text-sm font-medium text-gray-700"> ID Ceneo </label>
|
||||
<input
|
||||
id="ceneoId"
|
||||
type="text"
|
||||
bind:value={gift.ceneo_id}
|
||||
class="w-full rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<p class="mt-2 text-xs text-gray-500">
|
||||
Jeśli produkt nie został jeszcze kupiony i zostanie podany jego ID na ceneo, bot będzie
|
||||
obserwował ceny i w razie zmian będzie informował o tym na discord.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="imageUrl" class="mb-2 block text-sm font-medium text-gray-700">
|
||||
URL obrazu
|
||||
</label>
|
||||
<input
|
||||
id="imageUrl"
|
||||
type="url"
|
||||
bind:value={gift.imageUrl}
|
||||
class="w-full rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{#snippet footer()}
|
||||
<div class="flex justify-end gap-3">
|
||||
<Button variant="secondary" onClick={onClose} disabled={isLoading}>Anuluj</Button>
|
||||
<Button variant="primary" onClick={handleSubmit} disabled={isLoading || !gift?.title?.trim()}>
|
||||
{isLoading ? 'Zapisywanie...' : 'Zapisz'}
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
89
src/lib/components/molecules/PersonCard.svelte
Normal file
89
src/lib/components/molecules/PersonCard.svelte
Normal file
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { refreshAll } from '$app/navigation';
|
||||
import { formatCurrency } from '$lib/helpers/formatCurrency';
|
||||
import { DB } from '$lib/integrations/db';
|
||||
import Button from '../atoms/Button.svelte';
|
||||
import Heading from '../atoms/Heading.svelte';
|
||||
import GiftCard from './GiftCard.svelte';
|
||||
import GiftModal from './GiftModal.svelte';
|
||||
import PersonModal from './PersonModal.svelte';
|
||||
|
||||
let person: Person = $props();
|
||||
const gifts = $derived(person.expand.gifts);
|
||||
const totalCost = $derived(gifts?.reduce((acc, gift) => acc + gift.cost, 0) || 0);
|
||||
|
||||
let editPersonModal = $state(false);
|
||||
let addGiftModal = $state(false);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="mt-6 rounded-2xl bg-gray-300 p-4 text-gray-700 shadow-sm transition-all duration-200 ease-in-out hover:shadow-md md:p-6"
|
||||
>
|
||||
<div
|
||||
class="mb-6 flex flex-col items-center gap-4 border-b border-gray-700 pb-4 sm:justify-between"
|
||||
>
|
||||
<Heading size="large" spacing="none">
|
||||
<span class="text-gray-700">
|
||||
{person.name}
|
||||
</span>
|
||||
</Heading>
|
||||
|
||||
<div class="flex flex-row items-center justify-center gap-2">
|
||||
<Button variant="primary" onClick={() => (editPersonModal = true)}>Edytuj</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={() => {
|
||||
DB.deletePerson(person.id).finally(() => {
|
||||
refreshAll();
|
||||
});
|
||||
}}>Usuń</Button
|
||||
>
|
||||
</div>
|
||||
<PersonModal
|
||||
isOpen={editPersonModal}
|
||||
onClose={() => (editPersonModal = false)}
|
||||
editPerson={person}
|
||||
title="Edytuj osobę"
|
||||
onSave={async (data) => {
|
||||
await DB.updatePerson(person.id, data);
|
||||
refreshAll();
|
||||
editPersonModal = false;
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if person.notes}
|
||||
<p class="text-gray-600">{person.notes}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500">
|
||||
<span>Ilość prezentów: {gifts?.length || 0}</span>
|
||||
<span>•</span>
|
||||
<span>Koszt: {formatCurrency(totalCost)}</span>
|
||||
</div>
|
||||
<div class="mt-8 mb-4 flex items-start justify-between gap-4">
|
||||
<Heading size="medium" spacing="none">
|
||||
<span class="text-gray-700">
|
||||
Prezenty <br />
|
||||
</span>
|
||||
</Heading>
|
||||
|
||||
<Button variant="secondary" onClick={() => (addGiftModal = true)}>+</Button>
|
||||
|
||||
<!-- <GiftCardAdd person={person} /> -->
|
||||
<GiftModal
|
||||
title="Dodaj nowy prezent"
|
||||
isOpen={addGiftModal}
|
||||
onClose={() => (addGiftModal = false)}
|
||||
onSave={async (data) => {
|
||||
await DB.createGift(data);
|
||||
refreshAll();
|
||||
addGiftModal = false;
|
||||
}}
|
||||
personId={person.id}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#each gifts as gift (gift.id)}
|
||||
<GiftCard {...gift} editable />
|
||||
{/each}
|
||||
</div>
|
||||
80
src/lib/components/molecules/PersonModal.svelte
Normal file
80
src/lib/components/molecules/PersonModal.svelte
Normal file
@@ -0,0 +1,80 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import type { DB } from '$lib/integrations/db';
|
||||
import Modal from '../atoms/Modal.svelte';
|
||||
import Button from '../atoms/Button.svelte';
|
||||
import { page } from '$app/state';
|
||||
|
||||
type CreatePerson = Parameters<typeof DB.createPerson>[0];
|
||||
|
||||
type Props = {
|
||||
onSave: (data: CreatePerson) => Promise<void>;
|
||||
editPerson?: Person;
|
||||
} & Omit<ComponentProps<typeof Modal>, 'children'>;
|
||||
|
||||
let { onSave, isOpen, onClose, editPerson }: Props = $props();
|
||||
|
||||
let person = $state<Person>(editPerson ? { ...editPerson } : ({} as Person));
|
||||
let isLoading = $state(false);
|
||||
|
||||
if (!person?.name) {
|
||||
person.name = '';
|
||||
person.notes = '';
|
||||
person.years = [page.params.year || new Date().getFullYear().toString()];
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (!person) return;
|
||||
|
||||
isLoading = true;
|
||||
try {
|
||||
await onSave(person);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} {onClose} title={person ? 'Edytuj osobę' : 'Dodaj nową osobę'}>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label for="name" class="mb-2 block text-sm font-medium text-gray-700">
|
||||
Imię i nazwisko *
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
type="text"
|
||||
class="w-full rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
disabled={isLoading}
|
||||
bind:value={person.name}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="notes" class="mb-2 block text-sm font-medium text-gray-700"> Notatki </label>
|
||||
<textarea
|
||||
id="notes"
|
||||
rows={4}
|
||||
class="w-full resize-none rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isLoading}
|
||||
bind:value={person.notes}
|
||||
></textarea>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{#snippet footer()}
|
||||
<div class="flex justify-end gap-3">
|
||||
<Button variant="secondary" onClick={onClose} disabled={isLoading}>Anuluj</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading || !person?.name?.trim()}
|
||||
>
|
||||
{isLoading ? 'Zapisywanie...' : 'Zapisz'}
|
||||
</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="secondary" href="/">Prezenty tego roku</Button>
|
||||
{#each years as year}
|
||||
<Button href={`/year/${year.year}`} variant="secondary">
|
||||
<Button href={`/rok/${year.year}`} variant="secondary">
|
||||
{year.year}
|
||||
</Button>
|
||||
{/each}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { flip } from 'svelte/animate';
|
||||
import GiftCard from '../molecules/GiftCard.svelte';
|
||||
import { cubicInOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
type Props = {
|
||||
gifts: Gift[];
|
||||
@@ -12,29 +12,43 @@
|
||||
let sort = $state<keyof Pick<Gift, 'title' | 'cost' | 'person' | 'status'>>('title');
|
||||
|
||||
let sortedGifts = $derived.by(() => {
|
||||
let newSortedGifts = [...gifts];
|
||||
if (sort === 'cost') {
|
||||
newSortedGifts.sort((a, b) => a.cost - b.cost);
|
||||
} else if (sort === 'person') {
|
||||
newSortedGifts.sort((a, b) => a.expand.person.name.localeCompare(b.expand.person.name));
|
||||
} else if (sort === 'status') {
|
||||
newSortedGifts.sort((a, b) => a.status.localeCompare(b.status));
|
||||
let out = [...gifts];
|
||||
|
||||
switch (sort) {
|
||||
case 'title':
|
||||
out.sort((a, b) => a.title.localeCompare(b.title));
|
||||
break;
|
||||
case 'cost':
|
||||
out.sort((a, b) => a.cost - b.cost);
|
||||
break;
|
||||
case 'person':
|
||||
out.sort((a, b) => a.expand.person.name.localeCompare(b.expand.person.name));
|
||||
break;
|
||||
case 'status':
|
||||
out.sort((a, b) => a.status.localeCompare(b.status));
|
||||
break;
|
||||
}
|
||||
return newSortedGifts;
|
||||
|
||||
return out;
|
||||
});
|
||||
|
||||
let filteredGifts = $derived.by(() => {
|
||||
if (filter === 'all') return sortedGifts;
|
||||
return sortedGifts.filter((gift) => gift.status === filter);
|
||||
let out = [...sortedGifts];
|
||||
|
||||
if (filter !== 'all') {
|
||||
out = out.filter((gift) => gift.status === filter);
|
||||
}
|
||||
|
||||
return out;
|
||||
});
|
||||
|
||||
let renderGifts = $derived(filteredGifts);
|
||||
let renderedGifts = $derived(filteredGifts);
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<div class="flex flex-row items-center justify-between gap-4">
|
||||
<select
|
||||
class="rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
class="ml-auto rounded-xl border border-gray-300 px-4 py-2 transition-all outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
|
||||
bind:value={filter}
|
||||
>
|
||||
<option value="all">Wszystkie</option>
|
||||
@@ -54,9 +68,13 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#each renderGifts as gift (gift.id)}
|
||||
<div animate:flip={{ duration: 400, easing: cubicInOut }}>
|
||||
<GiftCard {...gift} />
|
||||
{#key filter}
|
||||
<div in:fly={{ x: 100, delay: 250, duration: 200 }} out:fly={{ x: -100, duration: 200 }}>
|
||||
{#each renderedGifts as gift (gift.id)}
|
||||
<div animate:flip={{ duration: 200 }}>
|
||||
<GiftCard {...gift} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/key}
|
||||
</section>
|
||||
|
||||
22
src/lib/components/organisms/YearControls.svelte
Normal file
22
src/lib/components/organisms/YearControls.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import { refreshAll } from '$app/navigation';
|
||||
import { DB } from '$lib/integrations/db';
|
||||
import ActionCard from '../molecules/ActionCard.svelte';
|
||||
import PersonModal from '../molecules/PersonModal.svelte';
|
||||
|
||||
let isOpen = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="my-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<ActionCard title="Dodaj nową osobę" onClick={() => (isOpen = true)} />
|
||||
</div>
|
||||
|
||||
<PersonModal
|
||||
{isOpen}
|
||||
onClose={() => (isOpen = false)}
|
||||
title="Dodaj nową osobę"
|
||||
onSave={async (data) => {
|
||||
await DB.createPerson(data);
|
||||
refreshAll();
|
||||
}}
|
||||
/>
|
||||
14
src/lib/helpers/formatStatus/index.ts
Normal file
14
src/lib/helpers/formatStatus/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const formatStatus = (status: Gift['status']) => {
|
||||
switch (status) {
|
||||
case 'planned':
|
||||
return 'Planowane';
|
||||
case 'decided':
|
||||
return 'Do kupienia';
|
||||
case 'bought':
|
||||
return 'Kupione';
|
||||
case 'ready':
|
||||
return 'Do spakowania';
|
||||
case 'wrapped':
|
||||
return 'Gotowe';
|
||||
}
|
||||
};
|
||||
@@ -18,7 +18,8 @@ export const DB = {
|
||||
return await pb.collection('gifts_person').getFirstListItem(`name = "${name}"`);
|
||||
},
|
||||
createPerson: async (data: Pick<DB.Person, 'name' | 'notes' | 'years'>): Promise<DB.Person> => {
|
||||
return await pb.collection('gifts_person').create({ ...data });
|
||||
const year = await pb.collection('gifts_year').getFirstListItem(`year = ${data.years[0]}`);
|
||||
return await pb.collection('gifts_person').create({ ...data, years: [year.id] });
|
||||
},
|
||||
updatePerson: async (id: string, data: Pick<DB.Person, 'name' | 'notes'>): Promise<Person> => {
|
||||
return await pb.collection('gifts_person').update(id, data);
|
||||
|
||||
5
src/lib/loaders/LoaderPerson.ts
Normal file
5
src/lib/loaders/LoaderPerson.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DB } from '$lib/integrations/db';
|
||||
|
||||
export const LoaderPersons = (yearId: string) => {
|
||||
return () => DB.getPersons(yearId);
|
||||
};
|
||||
19
src/routes/rok/[year]/+page.server.ts
Normal file
19
src/routes/rok/[year]/+page.server.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { orchestrateLoaders } from '$lib/loaders';
|
||||
import { LoaderPersons } from '$lib/loaders/LoaderPerson';
|
||||
import { LoaderYear } from '$lib/loaders/LoaderYear';
|
||||
import { LoaderYears } from '$lib/loaders/LoaderYear';
|
||||
|
||||
export const load = async ({ params }: { params: { year: string } }) => {
|
||||
const [yearData, yearsData] = await orchestrateLoaders([
|
||||
LoaderYear(Number(params.year)),
|
||||
LoaderYears()
|
||||
]);
|
||||
|
||||
const personsData = await LoaderPersons(yearData.id)();
|
||||
|
||||
return {
|
||||
year: yearData,
|
||||
years: yearsData,
|
||||
persons: personsData
|
||||
};
|
||||
};
|
||||
17
src/routes/rok/[year]/+page.svelte
Normal file
17
src/routes/rok/[year]/+page.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
import PersonCard from '$lib/components/molecules/PersonCard.svelte';
|
||||
import YearNav from '$lib/components/molecules/YearNav.svelte';
|
||||
import YearControls from '$lib/components/organisms/YearControls.svelte';
|
||||
import YearOverview from '$lib/components/organisms/YearOverview.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<YearOverview data={data.year} />
|
||||
<YearNav years={data.years} />
|
||||
<YearControls />
|
||||
<section class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each data.persons as person}
|
||||
<PersonCard {...person} />
|
||||
{/each}
|
||||
</section>
|
||||
@@ -1,4 +1,4 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
@@ -18,6 +18,7 @@ const config = {
|
||||
warningFilter: (warning) => {
|
||||
const ignoreCodes = [
|
||||
'a11y_no_static_element_interactions',
|
||||
'a11y_no_noninteractive_element_interactions',
|
||||
'a11y_click_events_have_key_events'
|
||||
];
|
||||
return !ignoreCodes.includes(warning.code);
|
||||
|
||||
Reference in New Issue
Block a user