Compare commits
5 Commits
c4b635b702
...
0fe4e22c4b
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fe4e22c4b | |||
| e9d1e9a319 | |||
| ff8f375955 | |||
| f731a7fce4 | |||
| f3b5dbbeee |
237
package-lock.json
generated
237
package-lock.json
generated
@ -9,9 +9,11 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.12",
|
||||
"pocketbase": "^0.22.1"
|
||||
"pocketbase": "^0.22.1",
|
||||
"uuid": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fsouza/prettierd": "^0.25.4",
|
||||
"@skeletonlabs/skeleton": "^2.10.3",
|
||||
"@skeletonlabs/tw-plugin": "^0.4.0",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
@ -19,6 +21,7 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.4.2",
|
||||
@ -58,6 +61,60 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
|
||||
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
|
||||
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz",
|
||||
"integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.3"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz",
|
||||
"integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
|
||||
@ -491,6 +548,24 @@
|
||||
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fsouza/prettierd": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@fsouza/prettierd/-/prettierd-0.25.4.tgz",
|
||||
"integrity": "sha512-y67VtstV11r2lD2ZF8owCPRgUXO4JjrBSAmqehzaaBBSXXzWtLEt3s6VQiHXXSgw0X2BBPUnyy+L2O21S4uiDA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"core_d": "^6.1.0",
|
||||
"prettier": "^3.3.3"
|
||||
},
|
||||
"bin": {
|
||||
"prettierd": "bin/prettierd"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@babel/parser": "^7.26.2",
|
||||
"@typescript-eslint/typescript-estree": "^8.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
@ -1079,6 +1154,75 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
|
||||
"integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
|
||||
"integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"@typescript-eslint/visitor-keys": "8.18.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
|
||||
"integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
@ -1424,6 +1568,16 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/core_d": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/core_d/-/core_d-6.1.0.tgz",
|
||||
"integrity": "sha512-vYgenhJ8CYCj+7LPbPdyFo2u0Doavfbi/vhFpR/BsW9/iUAhuKd+sw2l4CHXhaXIo4/058p2nlsAtbL7iswm5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"supports-color": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -1616,6 +1770,20 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esm-env": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
|
||||
@ -1806,6 +1974,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
@ -3240,6 +3418,20 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
@ -3435,6 +3627,22 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@ -3656,6 +3864,20 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
|
||||
"integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
@ -3738,6 +3960,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
|
||||
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz",
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fsouza/prettierd": "^0.25.4",
|
||||
"@skeletonlabs/skeleton": "^2.10.3",
|
||||
"@skeletonlabs/tw-plugin": "^0.4.0",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
@ -17,6 +18,7 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.4.2",
|
||||
@ -30,6 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.12",
|
||||
"pocketbase": "^0.22.1"
|
||||
"pocketbase": "^0.22.1",
|
||||
"uuid": "^11.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
86
src/lib/components/Dropdown.svelte
Normal file
86
src/lib/components/Dropdown.svelte
Normal file
@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { ListBox, ListBoxItem, popup, type PopupSettings } from "@skeletonlabs/skeleton";
|
||||
import type { Snippet } from "svelte";
|
||||
import type { HTMLInputAttributes } from "svelte/elements";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import UserIcon from "./svg/UserIcon.svelte";
|
||||
|
||||
export interface DropdownOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface SearchProps extends HTMLInputAttributes {
|
||||
children: Snippet;
|
||||
|
||||
/** Placeholder for the empty input element */
|
||||
placeholder?: string;
|
||||
|
||||
/** Form name of the input element, to reference input data after form submission */
|
||||
name?: string;
|
||||
|
||||
/** Manually set the label width, to align multiple inputs vertically. Supply value in CSS units. */
|
||||
labelwidth?: string;
|
||||
|
||||
/** The variable to bind to the input element. Has to be a [$state] so its value can be updated with the input element's contents. */
|
||||
input_variable: string;
|
||||
|
||||
/** The ID of the popup to trigger. UUID by default. */
|
||||
popup_id?: string;
|
||||
|
||||
/** The [PopupSettings] object for the popup to trigger. */
|
||||
popup_settings?: PopupSettings;
|
||||
|
||||
/** The options this autocomplete component allows to choose from.
|
||||
* Example: [[{ label: "Aston", value: "0" }, { label: "VCARB", value: "1" }]].
|
||||
*/
|
||||
options: DropdownOption[];
|
||||
}
|
||||
|
||||
let {
|
||||
children,
|
||||
placeholder = "",
|
||||
name = "",
|
||||
labelwidth = "auto",
|
||||
input_variable,
|
||||
popup_id = uuid(),
|
||||
popup_settings = {
|
||||
event: "click",
|
||||
target: popup_id,
|
||||
placement: "bottom",
|
||||
closeQuery: ".listbox-item",
|
||||
},
|
||||
options,
|
||||
...restProps
|
||||
}: SearchProps = $props();
|
||||
|
||||
const get_label = (value: string): string | undefined => {
|
||||
return options.find((o) => o.value === value)?.label;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
|
||||
<div
|
||||
class="input-group-shim select-none text-nowrap text-neutral-900"
|
||||
style="width: {labelwidth};"
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
<input
|
||||
use:popup={popup_settings}
|
||||
type="text"
|
||||
value={get_label(input_variable) ?? placeholder}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-popup={popup_id} class="card z-10 w-auto p-2 shadow">
|
||||
<ListBox>
|
||||
{#each options as option}
|
||||
<ListBoxItem bind:group={input_variable} {name} value={option.value}
|
||||
>{option.label}</ListBoxItem
|
||||
>
|
||||
{/each}
|
||||
</ListBox>
|
||||
<div class="bg-surface-100-800-token arrow"></div>
|
||||
</div>
|
||||
83
src/lib/components/Search.svelte
Normal file
83
src/lib/components/Search.svelte
Normal file
@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Autocomplete,
|
||||
popup,
|
||||
type AutocompleteOption,
|
||||
type PopupSettings,
|
||||
} from "@skeletonlabs/skeleton";
|
||||
import type { Snippet } from "svelte";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
interface SearchProps {
|
||||
children: Snippet;
|
||||
|
||||
/** Placeholder for the empty input element */
|
||||
placeholder?: string;
|
||||
|
||||
/** Form name of the input element, to reference input data after form submission */
|
||||
name?: string;
|
||||
|
||||
/** Manually set the label width, to align multiple inputs vertically. Supply value in CSS units. */
|
||||
labelwidth?: string;
|
||||
|
||||
/** The variable to bind to the input element. Has to be a [$state] so its value can be updated with the input element's contents. */
|
||||
input_variable: string;
|
||||
|
||||
/** The ID of the input element. UUID by default. */
|
||||
input_id?: string;
|
||||
|
||||
/** The ID of the popup to trigger. UUID by default. */
|
||||
popup_id?: string;
|
||||
|
||||
/** The [PopupSettings] object for the popup to trigger. */
|
||||
popup_settings?: PopupSettings;
|
||||
|
||||
/** The event handler updating the [input_variable] after selection. */
|
||||
selection_handler?: (event: CustomEvent<AutocompleteOption<string>>) => void;
|
||||
|
||||
/** The options this autocomplete component allows to choose from.
|
||||
* Example: [[{ label: "Aston", value: "0" }, { label: "VCARB", value: "1" }]].
|
||||
*/
|
||||
options: AutocompleteOption<string, unknown>[];
|
||||
}
|
||||
|
||||
let {
|
||||
children,
|
||||
placeholder = "",
|
||||
name = "",
|
||||
labelwidth = "auto",
|
||||
input_variable,
|
||||
input_id = uuid(),
|
||||
popup_id = uuid(),
|
||||
popup_settings = {
|
||||
event: "focus-click",
|
||||
target: popup_id,
|
||||
placement: "bottom",
|
||||
},
|
||||
selection_handler = (event: CustomEvent<AutocompleteOption<string>>): void => {
|
||||
input_variable = event.detail.label;
|
||||
},
|
||||
options,
|
||||
}: SearchProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr_auto]">
|
||||
<div
|
||||
class="input-group-shim select-none text-nowrap text-neutral-900"
|
||||
style="width: {labelwidth};"
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
<input
|
||||
id={input_id}
|
||||
type="search"
|
||||
{placeholder}
|
||||
{name}
|
||||
bind:value={input_variable}
|
||||
use:popup={popup_settings}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-popup={popup_id} class="card z-10 w-auto p-2 shadow" tabindex="-1">
|
||||
<Autocomplete bind:input={input_variable} {options} on:selection={selection_handler} />
|
||||
</div>
|
||||
@ -1,9 +1,22 @@
|
||||
import Input from "./Input.svelte";
|
||||
import Button from "./Button.svelte";
|
||||
import Card from "./Card.svelte";
|
||||
import Search from "./Search.svelte";
|
||||
import Dropdown from "./Dropdown.svelte";
|
||||
// import type DropdownOption from "./Dropdown.svelte";
|
||||
|
||||
import MenuDrawerIcon from "./svg/MenuDrawerIcon.svelte";
|
||||
import UserIcon from "./svg/UserIcon.svelte";
|
||||
import PasswordIcon from "./svg/PasswordIcon.svelte";
|
||||
|
||||
export { Input, Button, Card, MenuDrawerIcon, UserIcon, PasswordIcon };
|
||||
export {
|
||||
Input,
|
||||
Button,
|
||||
Card,
|
||||
Search,
|
||||
Dropdown,
|
||||
// type DropdownOption,
|
||||
MenuDrawerIcon,
|
||||
UserIcon,
|
||||
PasswordIcon,
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Retrieve an arbitrary object with a matching ID from an Array.
|
||||
* Supposed to use on collections returned by the PocketBase API.
|
||||
* Supposed to be used on collections returned by the PocketBase API.
|
||||
*/
|
||||
export const get_by_id = <T extends object>(objects: Array<T>, id: string): T | undefined => {
|
||||
export const get_by_id = <T extends object>(objects: T[], id: string): T | undefined => {
|
||||
return objects.find((o: T) => ("id" in o ? o.id === id : false));
|
||||
};
|
||||
|
||||
@ -38,12 +38,37 @@ export const actions = {
|
||||
},
|
||||
|
||||
create_driver: async ({ request, locals }) => {
|
||||
if (!locals.admin) return { unauthorized: true };
|
||||
|
||||
const data: FormData = form_data_clean(await request.formData());
|
||||
form_data_ensure_keys(data, ["firstname", "lastname", "code", "team", "headshot", "active"]);
|
||||
|
||||
console.log(data);
|
||||
|
||||
const record: Driver = await locals.pb.collection("drivers").create(data);
|
||||
|
||||
return { tab: 1 };
|
||||
},
|
||||
|
||||
update_driver: async ({ request, locals }) => {
|
||||
if (!locals.admin) return { unauthorized: true };
|
||||
|
||||
const data: FormData = form_data_clean(await request.formData());
|
||||
const id: string = form_data_get_and_remove_id(data);
|
||||
|
||||
const record: Driver = await locals.pb.collection("drivers").update(id, data);
|
||||
|
||||
return { tab: 1 };
|
||||
},
|
||||
|
||||
delete_driver: async ({ request, locals }) => {
|
||||
if (!locals.admin) return { unauthorized: true };
|
||||
|
||||
const data: FormData = form_data_clean(await request.formData());
|
||||
const id: string = form_data_get_and_remove_id(data);
|
||||
|
||||
await locals.pb.collection("drivers").delete(id);
|
||||
|
||||
return { tab: 1 };
|
||||
},
|
||||
|
||||
@ -70,8 +95,8 @@ export const actions = {
|
||||
|
||||
// This "load" function runs serverside only, as it's located inside +page.server.ts
|
||||
export const load: PageServerLoad = async ({ fetch, locals }) => {
|
||||
const fetch_teams = async (): Promise<Array<Team>> => {
|
||||
const teams: Array<Team> = await locals.pb.collection("teams").getFullList({
|
||||
const fetch_teams = async (): Promise<Team[]> => {
|
||||
const teams: Team[] = await locals.pb.collection("teams").getFullList({
|
||||
sort: "+name",
|
||||
fetch: fetch,
|
||||
});
|
||||
@ -83,8 +108,8 @@ export const load: PageServerLoad = async ({ fetch, locals }) => {
|
||||
return teams;
|
||||
};
|
||||
|
||||
const fetch_drivers = async (): Promise<Array<Driver>> => {
|
||||
const drivers: Array<Driver> = await locals.pb.collection("drivers").getFullList({
|
||||
const fetch_drivers = async (): Promise<Driver[]> => {
|
||||
const drivers: Driver[] = await locals.pb.collection("drivers").getFullList({
|
||||
sort: "+lastname",
|
||||
fetch: fetch,
|
||||
});
|
||||
@ -96,11 +121,11 @@ export const load: PageServerLoad = async ({ fetch, locals }) => {
|
||||
return drivers;
|
||||
};
|
||||
|
||||
const fetch_races = async (): Promise<Array<Race>> => {
|
||||
const fetch_races = async (): Promise<Race[]> => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const fetch_substitutions = async (): Promise<Array<Substitution>> => {
|
||||
const fetch_substitutions = async (): Promise<Substitution[]> => {
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Input, Button, Card } from "$lib/components";
|
||||
import { Input, Button, Card, Search, Dropdown } from "$lib/components";
|
||||
import { get_image_preview_event_handler } from "$lib/image";
|
||||
import { get_by_id } from "$lib/database";
|
||||
import type { Team } from "$lib/schema";
|
||||
|
||||
import type { Driver, Team } from "$lib/schema";
|
||||
import { type PageData, type ActionData } from "./$types";
|
||||
import { FileDropzone, Tab, TabGroup } from "@skeletonlabs/skeleton";
|
||||
import { FileDropzone, Tab, TabGroup, type AutocompleteOption } from "@skeletonlabs/skeleton";
|
||||
|
||||
// TODO: Why does this work but import { type DropdownOption } from "$lib/components" does not?
|
||||
import type { DropdownOption } from "$lib/components/Dropdown.svelte";
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
|
||||
@ -14,6 +16,17 @@
|
||||
// console.log(`Form returned current_tab=${form.current_tab}`);
|
||||
current_tab = form.tab;
|
||||
}
|
||||
|
||||
// Maps driver to team: <driver.id, team.id>
|
||||
let create_driver_team_select_value: string = $state("");
|
||||
let update_driver_team_select_values: { [key: string]: string } = $state({});
|
||||
data.drivers.forEach((driver: Driver) => {
|
||||
update_driver_team_select_values[driver.id] = driver.team;
|
||||
});
|
||||
|
||||
const driver_team_select_options: DropdownOption[] = data.teams.map((team: Team) => {
|
||||
return { label: team.name, value: team.id } as DropdownOption;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@ -121,8 +134,6 @@
|
||||
<!-- Drivers Tab -->
|
||||
<!-- Drivers Tab -->
|
||||
|
||||
<!-- TODO: Team select -->
|
||||
<!-- TODO: Active switch -->
|
||||
<div class="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6">
|
||||
<!-- List all drivers inside the database -->
|
||||
{#each data.drivers as driver}
|
||||
@ -155,9 +166,14 @@
|
||||
labelwidth="120px"
|
||||
disabled={!data.admin}>Driver Code</Input
|
||||
>
|
||||
<Button>
|
||||
<img src={get_by_id<Team>(data.teams, driver.team)?.logo_url} alt="" />
|
||||
</Button>
|
||||
|
||||
<!-- Driver team input -->
|
||||
<Dropdown
|
||||
name="team"
|
||||
input_variable={update_driver_team_select_values[driver.id]}
|
||||
labelwidth="120px"
|
||||
options={driver_team_select_options}>Team</Dropdown
|
||||
>
|
||||
|
||||
<!-- Headshot upload -->
|
||||
<FileDropzone
|
||||
@ -201,7 +217,6 @@
|
||||
<Input
|
||||
id="driver_first_name_create"
|
||||
name="firstname"
|
||||
placeholder="First Name"
|
||||
labelwidth="120px"
|
||||
disabled={!data.admin}
|
||||
required>First Name</Input
|
||||
@ -209,7 +224,6 @@
|
||||
<Input
|
||||
id="driver_last_name_create"
|
||||
name="lastname"
|
||||
placeholder="Last Name"
|
||||
labelwidth="120px"
|
||||
disabled={!data.admin}
|
||||
required>Last Name</Input
|
||||
@ -217,12 +231,22 @@
|
||||
<Input
|
||||
id="driver_code_create"
|
||||
name="code"
|
||||
placeholder="Driver Code"
|
||||
labelwidth="120px"
|
||||
disabled={!data.admin}
|
||||
maxlength={3}
|
||||
minlength={3}
|
||||
required>Driver Code</Input
|
||||
>
|
||||
|
||||
<!-- Driver team input -->
|
||||
<Dropdown
|
||||
input_variable={create_driver_team_select_value}
|
||||
name="team"
|
||||
labelwidth="120px"
|
||||
options={driver_team_select_options}
|
||||
required>Team</Dropdown
|
||||
>
|
||||
|
||||
<!-- Headshot upload -->
|
||||
<FileDropzone
|
||||
name="headshot"
|
||||
|
||||
Reference in New Issue
Block a user