init
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
db
|
19
jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
3298
package-lock.json
generated
Normal file
35
package.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "wdb",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
||||||
|
"tauri": "tauri"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.1.1",
|
||||||
|
"@tauri-apps/plugin-opener": "^2.2.2",
|
||||||
|
"@tauri-apps/plugin-fs": "~2.0.4",
|
||||||
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-sql": "~2.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/adapter-static": "^3.0.8",
|
||||||
|
"@sveltejs/kit": "^2.15.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
|
"svelte": "^5.15.0",
|
||||||
|
"svelte-check": "^4.1.1",
|
||||||
|
"typescript": "~5.6.3",
|
||||||
|
"vite": "^6.0.5",
|
||||||
|
"@tauri-apps/cli": "^2.1.0",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"tailwindcss": "^3.4.17"
|
||||||
|
}
|
||||||
|
}
|
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
7
src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Generated by Tauri
|
||||||
|
# will have schema files for capabilities auto-completion
|
||||||
|
/gen/schemas
|
5768
src-tauri/Cargo.lock
generated
Normal file
26
src-tauri/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "wdb"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "wdb ais"
|
||||||
|
authors = ["Relaxed"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# The `_lib` suffix may seem redundant but it is necessary
|
||||||
|
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||||
|
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||||
|
name = "wdb_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
tauri-plugin-opener = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tauri-plugin-shell = "2.0.0-rc"
|
||||||
|
tauri-plugin-sql = { version = "2.0.0-rc", features = ["sqlite"] }
|
3
src-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
14
src-tauri/capabilities/default.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "Capability for the main window",
|
||||||
|
"windows": ["main"],
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"opener:default",
|
||||||
|
"shell:allow-open",
|
||||||
|
"sql:default",
|
||||||
|
"sql:allow-load",
|
||||||
|
"sql:allow-execute"
|
||||||
|
]
|
||||||
|
}
|
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 903 B |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
1
src-tauri/old_gen/schemas/acl-manifests.json
Executable file
1
src-tauri/old_gen/schemas/capabilities.json
Executable file
@ -0,0 +1 @@
|
|||||||
|
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","opener:default","shell:allow-open","sql:default","sql:allow-load","sql:allow-execute"]}}
|
2316
src-tauri/old_gen/schemas/desktop-schema.json
Executable file
2049
src-tauri/old_gen/schemas/linux-schema.json
Executable file
2316
src-tauri/old_gen/schemas/windows-schema.json
Executable file
9
src-tauri/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.plugin(tauri_plugin_sql::Builder::new().build())
|
||||||
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
6
src-tauri/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
sooome_lib::run()
|
||||||
|
}
|
35
src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "wdb",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "ru.ogkod.wdb",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "npm run dev",
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"beforeBuildCommand": "npm run build",
|
||||||
|
"frontendDist": "../build"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "wdb",
|
||||||
|
"width": 800,
|
||||||
|
"height": 600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
33
src/app.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" class="h-full
|
||||||
|
bg-[var(--w-bg)] dark:bg-[var(--b-bg)]
|
||||||
|
text-black dark:text-[var(--b-text)] ">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<!-- <link rel="icon" href="%sveltekit.assets%/favicon.png" /> -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>Tauri + SvelteKit App</title>
|
||||||
|
<script>
|
||||||
|
if (
|
||||||
|
localStorage.getItem("color-theme") === "dark" ||
|
||||||
|
(!("color-theme" in localStorage) &&
|
||||||
|
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||||
|
) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">
|
||||||
|
<div class="flex flex-col h-svh">
|
||||||
|
%sveltekit.body%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
18
src/lib/components/form/DateInput.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let value;
|
||||||
|
export let is_required = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_required}
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
required
|
||||||
|
bind:value
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input type="date" bind:value class="w-full bg-black text-white p-2 mx-2" />
|
||||||
|
{/if}
|
8
src/lib/components/form/Field.svelte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
export let name = "";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-auto flex justify-start items-center my-1">
|
||||||
|
<h1>{name}</h1>
|
||||||
|
<slot />
|
||||||
|
</div>
|
64
src/lib/components/form/NumberInput.svelte
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<script>
|
||||||
|
export let value = 0;
|
||||||
|
export let is_float = false;
|
||||||
|
export let min = 0;
|
||||||
|
export let max = 999999999;
|
||||||
|
export let step = "0.1";
|
||||||
|
export let is_required = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_float && is_required}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
{min}
|
||||||
|
{max}
|
||||||
|
{step}
|
||||||
|
{value}
|
||||||
|
required
|
||||||
|
on:input={(e) => (value = parseFloat(e.target.value))}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{:else if is_float}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
{min}
|
||||||
|
{max}
|
||||||
|
{step}
|
||||||
|
{value}
|
||||||
|
on:input={(e) => (value = parseFloat(e.target.value))}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !is_float && is_required}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
{min}
|
||||||
|
{max}
|
||||||
|
{value}
|
||||||
|
required
|
||||||
|
on:input={(e) => (value = Number(e.target.value))}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{:else if !is_float}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
{min}
|
||||||
|
{max}
|
||||||
|
{value}
|
||||||
|
on:input={(e) => (value = Number(e.target.value))}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{/if}
|
104
src/lib/components/form/PhoneInput.svelte
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
const isNumericInput = (event) => {
|
||||||
|
const key = event.keyCode;
|
||||||
|
// Allow number line
|
||||||
|
return (
|
||||||
|
(key >= 48 && key <= 57) ||
|
||||||
|
// Allow number pad
|
||||||
|
(key >= 96 && key <= 105)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isModifierKey = (event) => {
|
||||||
|
const key = event.keyCode;
|
||||||
|
// Allow Shift, Home, End
|
||||||
|
return (
|
||||||
|
event.shiftKey === true ||
|
||||||
|
key === 35 ||
|
||||||
|
key === 36 ||
|
||||||
|
// Allow Backspace, Tab, Enter, Delete
|
||||||
|
key === 8 ||
|
||||||
|
key === 9 ||
|
||||||
|
key === 13 ||
|
||||||
|
key === 46 ||
|
||||||
|
// Allow left, up, right, down
|
||||||
|
(key > 36 && key < 41) ||
|
||||||
|
// Allow Ctrl/Command + A,C,V,X,Z
|
||||||
|
((event.ctrlKey === true || event.metaKey === true) &&
|
||||||
|
(key === 65 ||
|
||||||
|
key === 67 ||
|
||||||
|
key === 86 ||
|
||||||
|
key === 88 ||
|
||||||
|
key === 90))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enforceFormat = (event) => {
|
||||||
|
// Input must be of a valid number format or a modifier key, and not longer than ten digits
|
||||||
|
if (!isNumericInput(event) && !isModifierKey(event)) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatToPhone = (event) => {
|
||||||
|
if (isModifierKey(event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First ten digits of input only
|
||||||
|
let input = event.target.value.replace(/\D/g, "").substring(1, 15);
|
||||||
|
|
||||||
|
const areaCode = input.substring(0, 3);
|
||||||
|
const middle = input.substring(3, 6);
|
||||||
|
const last = input.substring(6, 8);
|
||||||
|
const last2 = input.substring(8, 10);
|
||||||
|
|
||||||
|
if (input.length >= 8) {
|
||||||
|
phone = `+7 (${areaCode}) ${middle} - ${last} - ${last2}`;
|
||||||
|
} else if (input.length > 6) {
|
||||||
|
phone = `+7 (${areaCode}) ${middle} - ${last}`;
|
||||||
|
} else if (input.length > 3) {
|
||||||
|
phone = `+7 (${areaCode}) ${middle}`;
|
||||||
|
} else if (input.length >= 0) {
|
||||||
|
phone = `+7 (${areaCode}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const inputElement = document.getElementById("phoneNumber");
|
||||||
|
inputElement.addEventListener("keydown", enforceFormat);
|
||||||
|
inputElement.addEventListener("keyup", formatToPhone);
|
||||||
|
});
|
||||||
|
|
||||||
|
const pattern = ".{22,}";
|
||||||
|
export let phone = "+7 ";
|
||||||
|
export let is_required = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_required}
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
placeholder="+7 (777) 777 - 77 - 77"
|
||||||
|
bind:value={phone}
|
||||||
|
{pattern}
|
||||||
|
required
|
||||||
|
id="phoneNumber"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
placeholder="+7 (777) 777 - 77 - 77"
|
||||||
|
bind:value={phone}
|
||||||
|
{pattern}
|
||||||
|
id="phoneNumber"
|
||||||
|
/>
|
||||||
|
{/if}
|
55
src/lib/components/form/Select.svelte
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<script>
|
||||||
|
export let value;
|
||||||
|
|
||||||
|
export let query;
|
||||||
|
export let is_required = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_required}
|
||||||
|
<select
|
||||||
|
required
|
||||||
|
bind:value
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white
|
||||||
|
appearance-none"
|
||||||
|
>
|
||||||
|
{#await query then vtypes}
|
||||||
|
{#each vtypes as vtype}
|
||||||
|
{#if vtype.id === value}
|
||||||
|
<option selected value={vtype.id}>
|
||||||
|
<slot {vtype} />
|
||||||
|
</option>
|
||||||
|
{:else}
|
||||||
|
<option value={vtype.id}>
|
||||||
|
<slot {vtype} />
|
||||||
|
</option>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
</select>
|
||||||
|
{:else}
|
||||||
|
<select
|
||||||
|
bind:value
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white
|
||||||
|
appearance-none"
|
||||||
|
>
|
||||||
|
{#await query then vtypes}
|
||||||
|
{#each vtypes as vtype}
|
||||||
|
{#if vtype.id === value}
|
||||||
|
<option selected value={vtype.id}>
|
||||||
|
<slot {vtype} />
|
||||||
|
</option>
|
||||||
|
{:else}
|
||||||
|
<option value={vtype.id}>
|
||||||
|
<slot {vtype} />
|
||||||
|
</option>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
</select>
|
||||||
|
{/if}
|
55
src/lib/components/form/TextInput.svelte
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<script>
|
||||||
|
export let is_input = true;
|
||||||
|
export let value = "";
|
||||||
|
export let min_len = 0;
|
||||||
|
export let max_len = 100;
|
||||||
|
export let is_required = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_input && is_required}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
minlength={min_len}
|
||||||
|
maxlength={max_len}
|
||||||
|
{value}
|
||||||
|
required
|
||||||
|
on:input={(e) => (value = e.target.value)}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{:else if is_input}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
minlength={min_len}
|
||||||
|
maxlength={max_len}
|
||||||
|
{value}
|
||||||
|
on:input={(e) => (value = e.target.value)}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !is_input && is_required}
|
||||||
|
<textarea
|
||||||
|
required
|
||||||
|
{value}
|
||||||
|
on:input={(e) => (value = e.target.value)}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
></textarea>
|
||||||
|
{:else if !is_input}
|
||||||
|
<textarea
|
||||||
|
{value}
|
||||||
|
on:input={(e) => (value = e.target.value)}
|
||||||
|
class="w-full p-2 mx-2
|
||||||
|
border-2 border-black dark:border-none
|
||||||
|
bg-white dark:bg-black
|
||||||
|
text-black dark:text-white"
|
||||||
|
></textarea>
|
||||||
|
{/if}
|
48
src/lib/components/shorts/TableButtonsWrap.svelte
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<script>
|
||||||
|
import Button from "../ui/Button.svelte";
|
||||||
|
import Search from "../ui/Search.svelte";
|
||||||
|
export let is_searching = false;
|
||||||
|
|
||||||
|
/** @type {any} */
|
||||||
|
export let current_item;
|
||||||
|
export let search_query_result = "";
|
||||||
|
export let columns = ["id", "name"];
|
||||||
|
export let current_table;
|
||||||
|
|
||||||
|
export let on_add_click = () => {};
|
||||||
|
|
||||||
|
export let toggle_add_dialog = () => {};
|
||||||
|
|
||||||
|
export let joins = "";
|
||||||
|
|
||||||
|
/** @typedef SearchParam
|
||||||
|
* @property {string} name
|
||||||
|
* @property {boolean} is_number
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Array<SearchParam>}*/
|
||||||
|
export let ex_params = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="m-2 flex space-x-2">
|
||||||
|
<Button
|
||||||
|
is_visible={!is_searching}
|
||||||
|
on_click={() => {
|
||||||
|
on_add_click();
|
||||||
|
toggle_add_dialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
<Search
|
||||||
|
bind:is_searching
|
||||||
|
bind:current_item
|
||||||
|
bind:search_query_result
|
||||||
|
{joins}
|
||||||
|
{ex_params}
|
||||||
|
{columns}
|
||||||
|
{current_table}
|
||||||
|
></Search>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot {is_searching} {search_query_result} />
|
35
src/lib/components/shorts/TableCUDButtons.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
import Button from "../ui/Button.svelte";
|
||||||
|
|
||||||
|
export let is_dialog_item_add = false;
|
||||||
|
export let on_add = async () => {};
|
||||||
|
export let on_save = async () => {};
|
||||||
|
export let on_delete = async () => {};
|
||||||
|
|
||||||
|
export let form = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_dialog_item_add}
|
||||||
|
<Button
|
||||||
|
on_click={async () => {
|
||||||
|
if (form === null || form.checkValidity()) {
|
||||||
|
await on_add();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<div class="flex w-max space-x-2">
|
||||||
|
<Button
|
||||||
|
on_click={async () => {
|
||||||
|
if (form === null || form.checkValidity()) {
|
||||||
|
await on_save();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
<Button on_click={on_delete}>Удалить</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
114
src/lib/components/shorts/TableListShort.svelte
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<script>
|
||||||
|
import Dialog from "../ui/Dialog.svelte";
|
||||||
|
import TableCudButtons from "../shorts/TableCUDButtons.svelte";
|
||||||
|
import Button from "../ui/Button.svelte";
|
||||||
|
import Table from "../table/Table.svelte";
|
||||||
|
import Field from "../form/Field.svelte";
|
||||||
|
import TextInput from "../form/TextInput.svelte";
|
||||||
|
import Search from "../ui/Search.svelte";
|
||||||
|
|
||||||
|
export let is_item_dialog_open = false;
|
||||||
|
export let is_dialog_item_add = false;
|
||||||
|
|
||||||
|
export let cur_dialog_name = "__example__";
|
||||||
|
export let current_table;
|
||||||
|
|
||||||
|
/** @type {any} */
|
||||||
|
export let current_item;
|
||||||
|
|
||||||
|
export let query;
|
||||||
|
|
||||||
|
export let db;
|
||||||
|
|
||||||
|
export let toggle_add_dialog = () => {};
|
||||||
|
export let open_item_edit = (/** @type {any} */ item) => {};
|
||||||
|
|
||||||
|
export let sql_insert_short = async (
|
||||||
|
/** @type {any} */ arg1,
|
||||||
|
/** @type {any} */ arg2,
|
||||||
|
) => {};
|
||||||
|
export let sql_update_short = async (
|
||||||
|
/** @type {any} */ arg1,
|
||||||
|
/** @type {any} */ arg2,
|
||||||
|
) => {};
|
||||||
|
export let sql_delete_short = () => {};
|
||||||
|
|
||||||
|
export let second_col_name = "name";
|
||||||
|
export let columns = ["id", "name"];
|
||||||
|
export let columns_display = columns;
|
||||||
|
|
||||||
|
// export let is_searching = false;
|
||||||
|
export let is_searching = false;
|
||||||
|
|
||||||
|
let search_query_result = "";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
bind:is_open={is_item_dialog_open}
|
||||||
|
on_close={() => {
|
||||||
|
is_dialog_item_add = false;
|
||||||
|
}}
|
||||||
|
name={cur_dialog_name}
|
||||||
|
let:form
|
||||||
|
>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<h1>{columns_display[0]} {current_item.id}</h1>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Field name={columns_display[1]}>
|
||||||
|
<slot name="second_value_input">
|
||||||
|
<TextInput bind:value={current_item[second_col_name]}></TextInput>
|
||||||
|
</slot>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<TableCudButtons
|
||||||
|
{form}
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
on_add={async () => {
|
||||||
|
await sql_insert_short(current_table, [second_col_name]);
|
||||||
|
}}
|
||||||
|
on_save={async () => {
|
||||||
|
await sql_update_short(current_table, [second_col_name]);
|
||||||
|
}}
|
||||||
|
on_delete={sql_delete_short}
|
||||||
|
></TableCudButtons>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<div class="m-2 flex space-x-2">
|
||||||
|
<Button
|
||||||
|
is_visible={!is_searching}
|
||||||
|
on_click={() => {
|
||||||
|
current_item = {
|
||||||
|
id: 0,
|
||||||
|
};
|
||||||
|
current_item[second_col_name] = "";
|
||||||
|
toggle_add_dialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Search
|
||||||
|
bind:is_searching
|
||||||
|
bind:current_item
|
||||||
|
bind:search_query_result
|
||||||
|
{columns}
|
||||||
|
{current_table}
|
||||||
|
></Search>
|
||||||
|
<!-- <Button -->
|
||||||
|
<!-- on_click={async () => { -->
|
||||||
|
<!-- await export_csv(data); -->
|
||||||
|
<!-- }} -->
|
||||||
|
<!-- > -->
|
||||||
|
<!-- CSV -->
|
||||||
|
<!-- </Button> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
bind:is_searching
|
||||||
|
search_query={db.select(search_query_result)}
|
||||||
|
{open_item_edit}
|
||||||
|
{query}
|
||||||
|
{columns}
|
||||||
|
{columns_display}
|
||||||
|
></Table>
|
20
src/lib/components/shorts/ViewTable.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
import Spinner from "../ui/Spinner.svelte";
|
||||||
|
import Table from "../table/Table.svelte";
|
||||||
|
import Tr from "../table/Tr.svelte";
|
||||||
|
import Atd from "../table/Atd.svelte";
|
||||||
|
|
||||||
|
export let query;
|
||||||
|
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
export let columns = [];
|
||||||
|
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
export let names = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Table {columns} {query} let:item let:index>
|
||||||
|
<Tr {index}>
|
||||||
|
<Atd {item} {names}></Atd>
|
||||||
|
</Tr>
|
||||||
|
</Table>
|
6
src/lib/components/state.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const theme_switch_is_resize = writable(false);
|
||||||
|
export const theme_switch_is_transparent = writable(false);
|
||||||
|
export const theme_switch_is_dark = writable(true);
|
||||||
|
export const theme_switch_is_update = writable(false);
|
14
src/lib/components/table/Atd.svelte
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
import Td from "./Td.svelte";
|
||||||
|
/** @type {any} */
|
||||||
|
export let item = {};
|
||||||
|
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
export let names = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each names as name}
|
||||||
|
<Td>
|
||||||
|
{item[name]}
|
||||||
|
</Td>
|
||||||
|
{/each}
|
74
src/lib/components/table/Table.svelte
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<script>
|
||||||
|
import Atd from "./Atd.svelte";
|
||||||
|
import Tr from "./Tr.svelte";
|
||||||
|
import Spinner from "../ui/Spinner.svelte";
|
||||||
|
// export let data;
|
||||||
|
export let columns = ["__empty__"];
|
||||||
|
export let columns_display = columns;
|
||||||
|
export let class_name = "";
|
||||||
|
export let query;
|
||||||
|
export let open_item_edit = (/** @type {any} */ item) => {};
|
||||||
|
|
||||||
|
export let is_searching = false;
|
||||||
|
export let search_query;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !is_searching}
|
||||||
|
{#await query}
|
||||||
|
<Spinner></Spinner>
|
||||||
|
{:then data}
|
||||||
|
<table class="w-max min-w-[200px] table-fixed {class_name}">
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
class="w-full sticky
|
||||||
|
bg-black text-white"
|
||||||
|
style="inset-block-start: 0;"
|
||||||
|
>
|
||||||
|
{#each columns_display as column_name}
|
||||||
|
<td class="w-max p-2 text-center">
|
||||||
|
{column_name}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{#each data as item, i}
|
||||||
|
<slot {item} index={i}>
|
||||||
|
<Tr on_click={() => open_item_edit(item)} index={i}>
|
||||||
|
<Atd {item} names={columns}></Atd>
|
||||||
|
</Tr>
|
||||||
|
</slot>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{:catch}
|
||||||
|
<h1>Не удалось загрузить данные</h1>
|
||||||
|
{/await}
|
||||||
|
{:else if search_query !== ""}
|
||||||
|
{#await search_query}
|
||||||
|
<Spinner></Spinner>
|
||||||
|
{:then data}
|
||||||
|
<table class="w-max min-w-[200px] table-fixed {class_name}">
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
class="w-full sticky
|
||||||
|
bg-black text-white"
|
||||||
|
style="inset-block-start: 0;"
|
||||||
|
>
|
||||||
|
{#each columns_display as column_name}
|
||||||
|
<td class="w-max p-2 text-center">
|
||||||
|
{column_name}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{#each data as item, i}
|
||||||
|
<slot {item} index={i}>
|
||||||
|
<Tr on_click={() => open_item_edit(item)} index={i}>
|
||||||
|
<Atd {item} names={columns}></Atd>
|
||||||
|
</Tr>
|
||||||
|
</slot>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{:catch}
|
||||||
|
<h1>Не удалось загрузить данные</h1>
|
||||||
|
{/await}
|
||||||
|
{/if}
|
5
src/lib/components/table/Td.svelte
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<td
|
||||||
|
class="p-2 border-[1px] border-black overflow-hidden text-ellipsis break-words"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</td>
|
18
src/lib/components/table/Tr.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script>
|
||||||
|
export let index = 0;
|
||||||
|
export let on_click = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- class="bg-white dark:bg-gray-800" -->
|
||||||
|
<!-- {index % 2 == 0 ? 'text-white' : 'text-black'} -->
|
||||||
|
<!-- {index % 2 == 0 ? 'bg-[#444]' : 'bg-[#dcdcdc]'}" -->
|
||||||
|
<tr
|
||||||
|
on:click={on_click}
|
||||||
|
class="
|
||||||
|
text-black dark:text-white
|
||||||
|
{index % 2 == 0
|
||||||
|
? 'bg-[#fff] dark:bg-gray-800'
|
||||||
|
: 'bg-[#e0e0e0] dark:bg-gray-700'}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</tr>
|
35
src/lib/components/ui/Button.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
export let is_visible = true;
|
||||||
|
export let on_click = () => {};
|
||||||
|
export let on_submit = () => {};
|
||||||
|
|
||||||
|
/** @type {'primary' | 'regular' | 'second'} */
|
||||||
|
export let type = "primary";
|
||||||
|
|
||||||
|
export let class_name = "";
|
||||||
|
|
||||||
|
let styles = {
|
||||||
|
// bg-[#6664AF] dark:bg-[#6664AF] text-white text-2xl
|
||||||
|
primary: `w-max p-4 py-2 my-4 rounded-lg text-2xl
|
||||||
|
bg-[var(--w-button-bg)] dark:bg-[var(--b-button-bg)]
|
||||||
|
text-[var(--w-accent-text)] dark:text-[var(--b-accent-text)]
|
||||||
|
shadow-[2px_2px_#888] hover:bg-black hover:shadow-[4px_4px_#888]
|
||||||
|
transition-all duration-200 ${class_name}`,
|
||||||
|
|
||||||
|
regular: `w-max p-4 py-2 my-4 rounded-lg
|
||||||
|
bg-[#333] dark:bg-[#333] text-white text-2xl
|
||||||
|
shadow-none hover:bg-black hover:shadow-[4px_4px_#888]
|
||||||
|
transition-all duration-200 ${class_name}`,
|
||||||
|
|
||||||
|
second: `w-max p-4 py-2 my-4
|
||||||
|
border-2 border-black dark:border-[var(--b-border)]
|
||||||
|
bg-[#f0f0f0] dark:bg-[#303030] text-2xl
|
||||||
|
shadow-[-2px_-2px_#000] dark:shadow-[-2px_-2px_var(--b-border)] ${class_name}`,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if is_visible}
|
||||||
|
<button type="submit" class={styles[type]} on:click={on_click}>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
{/if}
|
98
src/lib/components/ui/Dialog.svelte
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<script>
|
||||||
|
// import ThemeSwitch from "./ThemeSwitch.svelte";
|
||||||
|
import x from "./x.svg";
|
||||||
|
export let is_open = false;
|
||||||
|
export let is_name_editable = false;
|
||||||
|
export let name = "";
|
||||||
|
export let class_name = "";
|
||||||
|
export let on_close = () => {
|
||||||
|
is_open = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dialog;
|
||||||
|
let local_open = false;
|
||||||
|
|
||||||
|
function toggle_dialog(_) {
|
||||||
|
if (is_open && local_open) {
|
||||||
|
dialog.showModal();
|
||||||
|
document.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (local_open === true) {
|
||||||
|
dialog.close();
|
||||||
|
document.removeEventListener(
|
||||||
|
"keydown",
|
||||||
|
(event) => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else if (!local_open) {
|
||||||
|
local_open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: _ = toggle_dialog(is_open);
|
||||||
|
|
||||||
|
let form;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog
|
||||||
|
bind:this={dialog}
|
||||||
|
class="top-0 left-0 w-full h-svh z-30
|
||||||
|
fixed overflow-hidden
|
||||||
|
max-w-full max-h-full
|
||||||
|
flex justify-center items-center
|
||||||
|
{is_open ? 'visible opacity-100' : 'invisible opacity-0'}
|
||||||
|
backdrop-blur-sm
|
||||||
|
bg-[#000000aa]
|
||||||
|
transition-all duration-300"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-[700px] h-[800px] mx-4 px-2 flex flex-col
|
||||||
|
border-[var(--w-border)] dark:border-[var(--b-border)] border-2 rounded-xl
|
||||||
|
bg-[var(--w-bg-second)] dark:bg-[var(--b-bg-second)]
|
||||||
|
text-[var(--w-text)] dark:text-[var(--b-text)] text-2xl
|
||||||
|
overflow-hidden
|
||||||
|
{class_name}"
|
||||||
|
>
|
||||||
|
<div class=" relative flex flex-initial justify-between items-center">
|
||||||
|
{#if is_name_editable}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={name}
|
||||||
|
class="bg-[var(--w-bg)] dark:bg-[var(--b-bg)] w-full focus-visible:outline-none"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<p class=" w-full focus-visible:outline-none">
|
||||||
|
{name}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
is_open = false;
|
||||||
|
on_close();
|
||||||
|
}}
|
||||||
|
class="size-[60px] m-2 pb-3"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={x}
|
||||||
|
class="size-[40px] dark:invert"
|
||||||
|
alt="x close img"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
bind:this={form}
|
||||||
|
action="/"
|
||||||
|
target="_self"
|
||||||
|
class="overflow-y-auto"
|
||||||
|
>
|
||||||
|
<slot {form}></slot>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
77
src/lib/components/ui/LoginForm.svelte
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<script>
|
||||||
|
import Button from "./Button.svelte";
|
||||||
|
let form;
|
||||||
|
let login = "";
|
||||||
|
let passoword = "";
|
||||||
|
|
||||||
|
export let is_logedin = false;
|
||||||
|
export let access_level = "";
|
||||||
|
export let check_login = async (l, p) => [false, ""];
|
||||||
|
let is_error = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !is_logedin}
|
||||||
|
<div
|
||||||
|
class="w-full h-full pr-4 overflow-y-auto
|
||||||
|
flex justify-center items-center
|
||||||
|
bg-[var(--w-bg-second)] dark:bg-[#111]"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
bind:this={form}
|
||||||
|
class="flex flex-col justify-center items-center"
|
||||||
|
>
|
||||||
|
<h1 class="text-4xl">Вход</h1>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="login"
|
||||||
|
bind:value={login}
|
||||||
|
required
|
||||||
|
class="w-[400px] m-2 p-2
|
||||||
|
text-black dark:text-white
|
||||||
|
bg-white dark:bg-black"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="password"
|
||||||
|
bind:value={passoword}
|
||||||
|
required
|
||||||
|
class="w-[400px] m-2 p-2
|
||||||
|
text-black dark:text-white
|
||||||
|
bg-white dark:bg-black"
|
||||||
|
/>
|
||||||
|
{#if is_error}
|
||||||
|
<h1>Неправильный логин или пароль</h1>
|
||||||
|
{/if}
|
||||||
|
<Button
|
||||||
|
class_name={"!w-[400px] !m-2 text-xl"}
|
||||||
|
on_click={async () => {
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
if (login === "admin" && passoword === "1234") {
|
||||||
|
is_logedin = true;
|
||||||
|
access_level = "admin";
|
||||||
|
is_error = false;
|
||||||
|
login = "";
|
||||||
|
passoword = "";
|
||||||
|
} else {
|
||||||
|
const [is_ok, access_type] = await check_login(
|
||||||
|
login,
|
||||||
|
passoword,
|
||||||
|
);
|
||||||
|
if (!is_ok) {
|
||||||
|
is_error = true;
|
||||||
|
} else {
|
||||||
|
is_error = false;
|
||||||
|
login = "";
|
||||||
|
passoword = "";
|
||||||
|
is_logedin = true;
|
||||||
|
access_level = access_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
57
src/lib/components/ui/NavBar.svelte
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<script>
|
||||||
|
import ThemeSwitch from "./ThemeSwitch.svelte";
|
||||||
|
import Button from "./Button.svelte";
|
||||||
|
|
||||||
|
// /** @type {'tables' | 'viewes'} */
|
||||||
|
export let current_page = "";
|
||||||
|
|
||||||
|
/** @type {any} */
|
||||||
|
export let db_scheme = {};
|
||||||
|
|
||||||
|
export let is_view_open = false;
|
||||||
|
|
||||||
|
export let check_access = (name) => false;
|
||||||
|
|
||||||
|
export let is_logedin = false;
|
||||||
|
|
||||||
|
export let on_logout = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full h-[80px] mb-2 pr-4 text-xl flex-initial
|
||||||
|
flex justify-between items-center
|
||||||
|
bg-[var(--w-bg-second)] dark:bg-[#111]"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
{#if is_logedin}
|
||||||
|
{#each Object.entries(db_scheme) as page_name}
|
||||||
|
{#if check_access(page_name[0])}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
is_view_open = false;
|
||||||
|
current_page = page_name[0];
|
||||||
|
}}
|
||||||
|
class="m-4 mx-2 p-4
|
||||||
|
{current_page === page_name[0]
|
||||||
|
? 'bg-[#d8efff] dark:bg-[#114040]'
|
||||||
|
: 'bg-[var(--w-button-bg)] dark:bg-black text-[var(--w-accent-text)] dark:text-[var(--b-accent-text)]'}
|
||||||
|
border-2 border-[var(--w-border)] dark:border-[var(--b-border)]"
|
||||||
|
>
|
||||||
|
{page_name[0]}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="space-x-4 flex justify-center items-center">
|
||||||
|
<ThemeSwitch></ThemeSwitch>
|
||||||
|
{#if is_logedin}
|
||||||
|
<Button
|
||||||
|
on_click={on_logout}
|
||||||
|
class_name={"!text-black hover:!text-white dark:!text-white bg-[var(--w-red)] dark:bg-[var(--b-red)]"}
|
||||||
|
>
|
||||||
|
Выход
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
192
src/lib/components/ui/ScreenWrap.svelte
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<script>
|
||||||
|
import Database from "@tauri-apps/plugin-sql";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import NavBar from "$lib/components/ui/NavBar.svelte";
|
||||||
|
import SideMenu from "$lib/components/ui/SideMenu.svelte";
|
||||||
|
import LoginForm from "$lib/components/ui/LoginForm.svelte";
|
||||||
|
|
||||||
|
function toggle_update() {
|
||||||
|
is_loaded = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
is_loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_add_dialog() {
|
||||||
|
is_item_dialog_open = true;
|
||||||
|
is_dialog_item_add = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Object} item */
|
||||||
|
function open_item_edit(item) {
|
||||||
|
is_item_dialog_open = true;
|
||||||
|
is_dialog_item_add = false;
|
||||||
|
current_item = structuredClone(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sql_delete_short() {
|
||||||
|
await db.execute(
|
||||||
|
`delete from ${current_table} where id = ${current_item.id}`,
|
||||||
|
);
|
||||||
|
is_item_dialog_open = false;
|
||||||
|
toggle_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {any} an */
|
||||||
|
function format_type_insert(an) {
|
||||||
|
if (typeof an === "string") {
|
||||||
|
return `"${an}"`;
|
||||||
|
} else if (an === null) {
|
||||||
|
return "NULL";
|
||||||
|
} else if (an === undefined) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return `${an}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} table_name
|
||||||
|
* @param {Array<string>} columns
|
||||||
|
*/
|
||||||
|
async function sql_insert_short(table_name, columns) {
|
||||||
|
let q = `insert into ${table_name} (${columns.join(", ")}) values (${columns
|
||||||
|
.map((el) => format_type_insert(current_item[el]))
|
||||||
|
.filter((el) => el !== null)
|
||||||
|
.join(", ")})`;
|
||||||
|
console.log(current_item, q);
|
||||||
|
await db.execute(q);
|
||||||
|
is_item_dialog_open = false;
|
||||||
|
toggle_update();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
@param {string} name
|
||||||
|
@param {any} an
|
||||||
|
*/
|
||||||
|
function format_type_update(name, an) {
|
||||||
|
if (typeof an === "string") {
|
||||||
|
return `${name} = "${an}"`;
|
||||||
|
} else if (an === null) {
|
||||||
|
return `${name} = NULL`;
|
||||||
|
} else if (an === undefined) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return `${name} = ${an}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {string} table_name
|
||||||
|
* @param {Array<string>} columns
|
||||||
|
*/
|
||||||
|
async function sql_update_short(table_name, columns) {
|
||||||
|
// let q = `UPDATE ${table_name} SET
|
||||||
|
// ${columns.map((el) => (typeof current_item[el] === "string" ? `${el} = "${current_item[el]}"` : `${el} = ${current_item[el]}`)).join(", ")}
|
||||||
|
// WHERE id = ${current_item.id}`;
|
||||||
|
let q = `UPDATE ${table_name} SET
|
||||||
|
${columns
|
||||||
|
.map((el) => format_type_update(el, current_item[el]))
|
||||||
|
.filter((el) => el !== null)
|
||||||
|
.join(", ")}
|
||||||
|
WHERE id = ${current_item.id}`;
|
||||||
|
// console.log(q);
|
||||||
|
await db.execute(q);
|
||||||
|
is_item_dialog_open = false;
|
||||||
|
toggle_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
export let db_scheme;
|
||||||
|
|
||||||
|
/** @type {any} */
|
||||||
|
export let current_item = {};
|
||||||
|
|
||||||
|
/** @type {Database} */
|
||||||
|
export let db;
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
|
let current_page = "Запросы";
|
||||||
|
|
||||||
|
let current_table = "";
|
||||||
|
let current_view = "";
|
||||||
|
let is_view_open = false;
|
||||||
|
|
||||||
|
export let is_item_dialog_open = false;
|
||||||
|
export let is_dialog_item_add = false;
|
||||||
|
|
||||||
|
let is_logedin = false;
|
||||||
|
|
||||||
|
export let data_access;
|
||||||
|
|
||||||
|
let access_level = "";
|
||||||
|
/**
|
||||||
|
* @param {string} access_level
|
||||||
|
* @param {string} tab_name
|
||||||
|
* @param {string | null} name
|
||||||
|
*/
|
||||||
|
export let check_access = (access_level, tab_name, name) => false;
|
||||||
|
/**
|
||||||
|
* @param {string} login
|
||||||
|
* @param {string} passowrd
|
||||||
|
*/
|
||||||
|
export let check_login = async (login, passowrd) => [false, ""];
|
||||||
|
|
||||||
|
let is_loaded = false;
|
||||||
|
export let load_db = async () => {};
|
||||||
|
onMount(async () => {
|
||||||
|
await load_db();
|
||||||
|
is_loaded = true;
|
||||||
|
});
|
||||||
|
$: cur_dialog_name = is_dialog_item_add
|
||||||
|
? `Добавить в ${db_scheme[current_page].tables[current_table]}`
|
||||||
|
: `Изменить ${db_scheme[current_page].tables[current_table]}`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavBar
|
||||||
|
{is_logedin}
|
||||||
|
{db_scheme}
|
||||||
|
bind:current_page
|
||||||
|
bind:is_view_open
|
||||||
|
check_access={(name) => check_access(access_level, name, null)}
|
||||||
|
on_logout={() => {
|
||||||
|
is_logedin = false;
|
||||||
|
// @ts-ignore
|
||||||
|
access_level = "";
|
||||||
|
current_page = "Запросы";
|
||||||
|
current_table = "";
|
||||||
|
current_view = "";
|
||||||
|
}}
|
||||||
|
></NavBar>
|
||||||
|
|
||||||
|
<div class="w-full h-[calc(100vh-88px)] flex">
|
||||||
|
<SideMenu
|
||||||
|
{is_logedin}
|
||||||
|
bind:current_table
|
||||||
|
bind:current_view
|
||||||
|
bind:is_view_open
|
||||||
|
db_scheme={db_scheme[current_page]}
|
||||||
|
check_access={(name) =>
|
||||||
|
check_access(access_level, current_page, name)}
|
||||||
|
></SideMenu>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full pr-4 overflow-y-auto rounded-tl-xl
|
||||||
|
bg-[var(--w-bg-second)] dark:bg-[#111]"
|
||||||
|
>
|
||||||
|
<LoginForm bind:is_logedin bind:access_level {check_login}
|
||||||
|
></LoginForm>
|
||||||
|
{#if is_loaded && is_logedin}
|
||||||
|
<slot
|
||||||
|
{current_page}
|
||||||
|
{current_table}
|
||||||
|
{current_view}
|
||||||
|
{is_view_open}
|
||||||
|
{cur_dialog_name}
|
||||||
|
{toggle_update}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
{open_item_edit}
|
||||||
|
{sql_update_short}
|
||||||
|
{sql_insert_short}
|
||||||
|
{sql_delete_short}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
164
src/lib/components/ui/Search.svelte
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
<script>
|
||||||
|
import Button from "./Button.svelte";
|
||||||
|
import ViewTable from "../shorts/ViewTable.svelte";
|
||||||
|
|
||||||
|
export let is_searching = false;
|
||||||
|
|
||||||
|
export let search_value = "";
|
||||||
|
export let search_query_result = "";
|
||||||
|
|
||||||
|
/** @type {any} */
|
||||||
|
export let current_item;
|
||||||
|
|
||||||
|
export let columns = ["id", "name"];
|
||||||
|
|
||||||
|
export let current_table = "";
|
||||||
|
|
||||||
|
export let joins = "";
|
||||||
|
|
||||||
|
/** @typedef SearchParam
|
||||||
|
* @property {string} name
|
||||||
|
* @property {boolean} is_number
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Array<SearchParam>}*/
|
||||||
|
export let ex_params = [];
|
||||||
|
|
||||||
|
export let is_contains = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} thing
|
||||||
|
* @returns {Number | NaN}
|
||||||
|
*/
|
||||||
|
function parse_number(thing) {
|
||||||
|
let num = NaN;
|
||||||
|
if (thing.includes(".")) {
|
||||||
|
num = parseFloat(thing);
|
||||||
|
} else {
|
||||||
|
num = Number(thing);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} param_name
|
||||||
|
* @param {string | null} value
|
||||||
|
* @param {string} sv
|
||||||
|
* @returns {string | null}
|
||||||
|
*/
|
||||||
|
function make_sql_param(param_name, value, sv) {
|
||||||
|
if (value !== null && typeof value === "string") {
|
||||||
|
if (is_contains) {
|
||||||
|
return `${param_name} LIKE "%${sv}%"`;
|
||||||
|
} else {
|
||||||
|
return `${param_name} LIKE "${sv}"`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = parse_number(sv);
|
||||||
|
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
return `${param_name} = ${sv}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_input(/** @type {any} */ _) {
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
let params = [];
|
||||||
|
|
||||||
|
const search_purified = search_value.trim();
|
||||||
|
|
||||||
|
const search_splitted = search_purified.split(" ");
|
||||||
|
|
||||||
|
columns.forEach((el) => {
|
||||||
|
const param = make_sql_param(el, current_item[el], search_purified);
|
||||||
|
|
||||||
|
if (param !== null) {
|
||||||
|
params.push(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
search_splitted.forEach((sp) => {
|
||||||
|
const param = make_sql_param(el, current_item[el], sp);
|
||||||
|
if (param !== null && !params.includes(param)) {
|
||||||
|
params.push(param);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ex_params.length !== 0) {
|
||||||
|
ex_params.forEach((el) => {
|
||||||
|
const param = make_sql_param(
|
||||||
|
el.name,
|
||||||
|
el.is_number ? null : "",
|
||||||
|
search_purified,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (param !== null) {
|
||||||
|
params.push(param);
|
||||||
|
}
|
||||||
|
search_splitted.forEach((sp) => {
|
||||||
|
const param = make_sql_param(
|
||||||
|
el.name,
|
||||||
|
el.is_number ? null : "",
|
||||||
|
sp,
|
||||||
|
);
|
||||||
|
if (param !== null && !params.includes(param)) {
|
||||||
|
params.push(param);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.length === 0) {
|
||||||
|
search_query_result = "";
|
||||||
|
} else {
|
||||||
|
search_query_result = `SELECT * FROM ${current_table} ${joins} WHERE ${params.join(" OR ")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(`SELECT * FROM ${current_table} \n${joins} \nWHERE \n${params.join(" OR ")}`);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<div class=" flex justify-start items-center">
|
||||||
|
<Button
|
||||||
|
on_click={() => {
|
||||||
|
is_searching = !is_searching;
|
||||||
|
}}
|
||||||
|
type={is_searching ? "second" : "primary"}
|
||||||
|
>
|
||||||
|
{#if is_searching}
|
||||||
|
{"<-"}
|
||||||
|
{:else}
|
||||||
|
Поиск
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{#if is_searching}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={search_value}
|
||||||
|
on:input={on_input}
|
||||||
|
class=" w-full mx-4 p-2 text-xl border-black border-2
|
||||||
|
bg-[#fff] dark:bg-gray-800
|
||||||
|
dark:text-white
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if is_searching}
|
||||||
|
<div class="w-max flex justify-center items-center text-2xl">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="size-7 mr-4"
|
||||||
|
checked
|
||||||
|
on:click={() => {
|
||||||
|
is_contains = !is_contains;
|
||||||
|
on_input("");
|
||||||
|
}}
|
||||||
|
/> похоже?
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
63
src/lib/components/ui/SideMenu.svelte
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<script>
|
||||||
|
export let current_table = "";
|
||||||
|
export let current_view = "";
|
||||||
|
export let is_view_open = false;
|
||||||
|
|
||||||
|
export let db_scheme = { tables: {}, viewes: {} };
|
||||||
|
|
||||||
|
export let check_access = (name) => false;
|
||||||
|
|
||||||
|
export let is_logedin = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-[240px] h-full text-xl p-3 mr-2 flex-initial rounded-tr-xl
|
||||||
|
bg-[var(--w-bg-second)] dark:bg-[var(--b-bg-second)]"
|
||||||
|
>
|
||||||
|
{#if is_logedin}
|
||||||
|
{#if Object.entries(db_scheme.viewes).length !== 0 && Object.entries(db_scheme.viewes).some( (el) => check_access(el[0]), )}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
is_view_open = !is_view_open;
|
||||||
|
}}
|
||||||
|
class="py-2 my-1 w-full
|
||||||
|
{is_view_open
|
||||||
|
? 'bg-[#d8efff] dark:bg-[#114040]'
|
||||||
|
: 'bg-[var(--w-button-bg)] dark:bg-black text-[var(--w-accent-text)] dark:text-[var(--b-accent-text)]'}
|
||||||
|
border-2 border-[var(--w-border)] dark:border-[var(--b-border)]"
|
||||||
|
>
|
||||||
|
Отчеты
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="my-3 border-b-4 rounded-xl border-cyan-500"></div>
|
||||||
|
{/if}
|
||||||
|
{#each !is_view_open ? Object.entries(db_scheme.tables) : Object.entries(db_scheme.viewes) as item_name}
|
||||||
|
{#if check_access(item_name[0])}
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
if (
|
||||||
|
`${current_table}` === item_name[0] ||
|
||||||
|
current_view === item_name[0]
|
||||||
|
) {
|
||||||
|
current_table = "";
|
||||||
|
current_view = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_view_open) {
|
||||||
|
current_table = item_name[0];
|
||||||
|
} else {
|
||||||
|
current_view = item_name[0];
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
class="py-2 px-1 my-1 w-full
|
||||||
|
{current_table === item_name[0] || current_view === item_name[0]
|
||||||
|
? 'bg-[#d8efff] dark:bg-[#114040]'
|
||||||
|
: 'bg-[var(--w-button-bg)] dark:bg-black text-[var(--w-accent-text)] dark:text-[var(--b-accent-text)]'}
|
||||||
|
border-2 border-[var(--w-border)] dark:border-[var(--b-border)]"
|
||||||
|
>
|
||||||
|
{item_name[1]}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
6
src/lib/components/ui/Spinner.svelte
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<span
|
||||||
|
class="size-[40px] min-w-[40px]
|
||||||
|
border-[5px] border-[var(--w-border)] dark:border-[var(--b-border)] rounded-[50%]
|
||||||
|
inline-block box-border animate-spin"
|
||||||
|
style="border-bottom-color: transparent;"
|
||||||
|
></span>
|
68
src/lib/components/ui/ThemeSwitch.svelte
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let is_resize = false;
|
||||||
|
let is_transparent = false;
|
||||||
|
let is_dark = true;
|
||||||
|
let is_update = false;
|
||||||
|
|
||||||
|
function handleSwitchDarkMode() {
|
||||||
|
setTimeout(() => {
|
||||||
|
is_transparent = true;
|
||||||
|
is_dark = !is_dark;
|
||||||
|
setTimeout(() => {
|
||||||
|
is_resize = false;
|
||||||
|
is_transparent = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
is_update = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
is_update = false;
|
||||||
|
}, 0);
|
||||||
|
}, 0);
|
||||||
|
}, 250);
|
||||||
|
}, 500);
|
||||||
|
is_resize = true;
|
||||||
|
|
||||||
|
const isDark = window.document.documentElement.classList.toggle("dark");
|
||||||
|
if (isDark) {
|
||||||
|
localStorage.setItem("color-theme", "dark");
|
||||||
|
} else {
|
||||||
|
localStorage.setItem("color-theme", "light");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (localStorage.getItem("color-theme") === "light") {
|
||||||
|
is_dark = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !is_update}
|
||||||
|
<div class="min-w-[40px] min-h-[40px] flex justify-center items-center">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div
|
||||||
|
class="group min-h-[50px] min-w-[50px] rounded-full flex justify-center items-center
|
||||||
|
{is_dark ? 'bg-[var(--w-bg)]' : 'bg-[var(--b-bg)]'}
|
||||||
|
"
|
||||||
|
on:click={handleSwitchDarkMode}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=" rounded-full
|
||||||
|
{!is_transparent
|
||||||
|
? is_dark
|
||||||
|
? 'group-hover:bg-[var(--b-bg)] dark:group-hover:bg-[var(--b-bg)]'
|
||||||
|
: 'group-hover:bg-[var(--w-bg)] dark:group-hover:bg-[var(--w-bg)]'
|
||||||
|
: ''}
|
||||||
|
|
||||||
|
{!is_transparent
|
||||||
|
? is_resize
|
||||||
|
? 'h-[50px] w-[50px]'
|
||||||
|
: 'h-[25px] w-[25px]'
|
||||||
|
: 'h-[25px] w-[25px]'}
|
||||||
|
transition-all duration-500 ease-in-out"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
2
src/lib/components/ui/x.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path fill="#000000" d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"/></svg>
|
After Width: | Height: | Size: 507 B |
52
src/lib/global.css
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: JetBrainsMono;
|
||||||
|
src: url(/jbmv.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
outline: none !important;
|
||||||
|
|
||||||
|
font-family: JetBrainsMono;
|
||||||
|
|
||||||
|
/* white theme */
|
||||||
|
/* --w-bg: #ddd; */
|
||||||
|
/* --w-bg-second: #ccc; */
|
||||||
|
/* --w-accent: #eee; */
|
||||||
|
/* --w-accent-text: #000; */
|
||||||
|
/* --w-text: #000; */
|
||||||
|
/* --w-border: #000; */
|
||||||
|
/* --w-button-bg: #fff; */
|
||||||
|
|
||||||
|
--w-bg: #fff;
|
||||||
|
--w-bg-second: #F4E8D3;
|
||||||
|
--w-accent: #67BA80;
|
||||||
|
--w-accent-text: #fff;
|
||||||
|
--w-text: #000;
|
||||||
|
--w-border: #000;
|
||||||
|
--w-button-bg: #67BA80;
|
||||||
|
|
||||||
|
--w-red: #fcc;
|
||||||
|
--w-green: #0f0;
|
||||||
|
--w-blue: #ccf;
|
||||||
|
--w-orange: #f80;
|
||||||
|
|
||||||
|
/* dark theme */
|
||||||
|
--b-bg: #333;
|
||||||
|
--b-bg-second: #111;
|
||||||
|
--b-accent: #6664AF;
|
||||||
|
--b-accent-text: #fff;
|
||||||
|
--b-text: #fff;
|
||||||
|
--b-border: #fff;
|
||||||
|
--b-button-bg: #6664AF;
|
||||||
|
|
||||||
|
--b-red: #800;
|
||||||
|
--b-green: #080;
|
||||||
|
--b-blue: #000066;
|
||||||
|
--b-orange: #f80;
|
||||||
|
}
|
5
src/routes/+layout.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Tauri doesn't have a Node.js server to do proper SSR
|
||||||
|
// so we will use adapter-static to prerender the app (SSG)
|
||||||
|
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
|
||||||
|
export const prerender = true;
|
||||||
|
export const ssr = false;
|
776
src/routes/+page.svelte
Normal file
@ -0,0 +1,776 @@
|
|||||||
|
<script>
|
||||||
|
import "$lib/global.css";
|
||||||
|
import { path } from "@tauri-apps/api";
|
||||||
|
import Database from "@tauri-apps/plugin-sql";
|
||||||
|
// import { writeTextFile, BaseDirectory } from "@tauri-apps/plugin-fs";
|
||||||
|
|
||||||
|
import ScreenWrap from "$lib/components/ui/ScreenWrap.svelte";
|
||||||
|
import Dialog from "$lib/components/ui/Dialog.svelte";
|
||||||
|
|
||||||
|
import Field from "$lib/components/form/Field.svelte";
|
||||||
|
import TextInput from "$lib/components/form/TextInput.svelte";
|
||||||
|
import NumberInput from "$lib/components/form/NumberInput.svelte";
|
||||||
|
// import DateInput from "$lib/components/form/DateInput.svelte";
|
||||||
|
import PhoneInput from "$lib/components/form/PhoneInput.svelte";
|
||||||
|
import Select from "$lib/components/form/Select.svelte";
|
||||||
|
|
||||||
|
import Table from "$lib/components/table/Table.svelte";
|
||||||
|
import Tr from "$lib/components/table/Tr.svelte";
|
||||||
|
import Td from "$lib/components/table/Td.svelte";
|
||||||
|
import Atd from "$lib/components/table/Atd.svelte";
|
||||||
|
|
||||||
|
import TableCudButtons from "$lib/components/shorts/TableCUDButtons.svelte";
|
||||||
|
import TableButtonsWrap from "$lib/components/shorts/TableButtonsWrap.svelte";
|
||||||
|
import ViewTable from "$lib/components/shorts/ViewTable.svelte";
|
||||||
|
import TableListShort from "$lib/components/shorts/TableListShort.svelte";
|
||||||
|
|
||||||
|
/** @type {Database} */
|
||||||
|
let db;
|
||||||
|
|
||||||
|
async function load_db() {
|
||||||
|
// dev
|
||||||
|
db = await Database.load(`sqlite:${await path.resolve()}/../db/data.db`);
|
||||||
|
|
||||||
|
// db = await Database.load(`sqlite:${await path.resolve()}/db/data.db`);
|
||||||
|
|
||||||
|
await db.execute(`
|
||||||
|
CREATE TABLE IF NOT EXISTS EmployeeStatus (id INTEGER PRIMARY KEY, name VARCHAR(256));
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Employees (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
employee_status_id INTEGER REFERENCES EmployeeStatus(id) ON DELETE CASCADE,
|
||||||
|
fname VARCHAR(100),
|
||||||
|
sname VARCHAR(100),
|
||||||
|
tname VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS AccountAccessLevel (id INTEGER PRIMARY KEY, name VARCHAR(256));
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Accounts (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
employee_id INTEGER REFERENCES Employees(id),
|
||||||
|
account_access_level_id INTEGER REFERENCES AccountAccessLevel(id),
|
||||||
|
login VARCHAR(256),
|
||||||
|
password VARCHAR(256)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Clients (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
phone VARCHAR(36),
|
||||||
|
fname VARCHAR(100),
|
||||||
|
sname VARCHAR(100),
|
||||||
|
tname VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS RequestStatus (id INTEGER PRIMARY KEY, name VARCHAR(256));
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS RequestType (id INTEGER PRIMARY KEY, name VARCHAR(256));
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS Requests (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
client_id INTEGER REFERENCES Clients(id) ON DELETE CASCADE,
|
||||||
|
employee_id INTEGER REFERENCES Employees(id) ON DELETE CASCADE,
|
||||||
|
request_status_id INTEGER REFERENCES RequestStatus(id) ON DELETE CASCADE DEFAULT 1,
|
||||||
|
request_type_id INTEGER REFERENCES RequestType(id) ON DELETE CASCADE,
|
||||||
|
initial_date DATE DEFAULT CURRENT_TIMESTAMP, -- not editable by user
|
||||||
|
description TEXT,
|
||||||
|
duration FLOAT NULL -- hours
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @typedef Page
|
||||||
|
* @property {Object} tables
|
||||||
|
* @property {Object} viewes
|
||||||
|
*/
|
||||||
|
|
||||||
|
const db_scheme = {
|
||||||
|
/** @type Page */
|
||||||
|
Запросы: {
|
||||||
|
/** @type {any} */
|
||||||
|
tables: {
|
||||||
|
Requests: "Запросы",
|
||||||
|
Clients: "Клиенты",
|
||||||
|
},
|
||||||
|
/** @type {any} */
|
||||||
|
viewes: {
|
||||||
|
RequestCount: "Количество заявок",
|
||||||
|
AvgRequestTime: "Среднее время заявок",
|
||||||
|
StatByType: "Статистика по типам",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/** @type Page */
|
||||||
|
Сотрудники: {
|
||||||
|
/** @type {any} */
|
||||||
|
tables: {
|
||||||
|
Employees: "Сотрудники",
|
||||||
|
Accounts: "Аккаунты",
|
||||||
|
AccountAccessLevel: "Доступ",
|
||||||
|
},
|
||||||
|
/** @type {any} */
|
||||||
|
viewes: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
/** @type Page */
|
||||||
|
Списки: {
|
||||||
|
/** @type {any} */
|
||||||
|
tables: {
|
||||||
|
RequestStatus: "Статус запроса",
|
||||||
|
RequestType: "Тип запроса",
|
||||||
|
EmployeeStatus: "Должность",
|
||||||
|
},
|
||||||
|
/** @type {any} */
|
||||||
|
viewes: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const data_access = {
|
||||||
|
admin: true,
|
||||||
|
dicrector: {
|
||||||
|
Запросы: [
|
||||||
|
"Requests",
|
||||||
|
"Clients",
|
||||||
|
"RequestCount",
|
||||||
|
"AvgRequestTime",
|
||||||
|
"StatByType",
|
||||||
|
],
|
||||||
|
Сотрудники: ["Employees", "Accounts"],
|
||||||
|
Списки: ["RequestStatus", "RequestType", "EmployeeStatus"],
|
||||||
|
},
|
||||||
|
|
||||||
|
worker: {
|
||||||
|
Запросы: ["Requests", "Clients"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {any} */
|
||||||
|
export let current_item = {};
|
||||||
|
|
||||||
|
let is_item_dialog_open = false;
|
||||||
|
let is_dialog_item_add = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} access_level
|
||||||
|
* @param {string} tab_name
|
||||||
|
* @param {string | null} name
|
||||||
|
*/
|
||||||
|
function check_access(access_level, tab_name, name) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (data_access[access_level] === true) return true;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (Object.hasOwn(data_access[access_level], tab_name) && name === null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return data_access[access_level][tab_name].includes(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param {string} login
|
||||||
|
@param {string} passowrd
|
||||||
|
*/
|
||||||
|
async function check_login(login, passowrd) {
|
||||||
|
const access_type =
|
||||||
|
await db.select(`SELECT AccountAccessLevel.name FROM Accounts
|
||||||
|
JOIN AccountAccessLevel ON AccountAccessLevel.id = Accounts.account_access_level_id
|
||||||
|
WHERE Accounts.login = "${login}" AND Accounts.password = "${passowrd}";`);
|
||||||
|
if (access_type.length === 0) {
|
||||||
|
return [false, ""];
|
||||||
|
} else {
|
||||||
|
return [true, access_type[0].name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ScreenWrap
|
||||||
|
{db}
|
||||||
|
{db_scheme}
|
||||||
|
{data_access}
|
||||||
|
{load_db}
|
||||||
|
{check_access}
|
||||||
|
{check_login}
|
||||||
|
bind:is_item_dialog_open
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
bind:current_item
|
||||||
|
let:current_page
|
||||||
|
let:current_table
|
||||||
|
let:current_view
|
||||||
|
let:is_view_open
|
||||||
|
let:cur_dialog_name
|
||||||
|
let:toggle_add_dialog
|
||||||
|
let:open_item_edit
|
||||||
|
let:sql_update_short
|
||||||
|
let:sql_insert_short
|
||||||
|
let:sql_delete_short
|
||||||
|
>
|
||||||
|
{#if !is_view_open && current_page === "Запросы"}
|
||||||
|
{#if current_table === "Clients"}
|
||||||
|
<Dialog
|
||||||
|
bind:is_open={is_item_dialog_open}
|
||||||
|
name={cur_dialog_name}
|
||||||
|
let:form
|
||||||
|
>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<h1>id {current_item.id}</h1>
|
||||||
|
{/if}
|
||||||
|
<Field name={"сотовый"}>
|
||||||
|
<PhoneInput bind:phone={current_item.phone}></PhoneInput>
|
||||||
|
</Field>
|
||||||
|
<Field name={"Имя"}>
|
||||||
|
<TextInput bind:value={current_item.fname}></TextInput>
|
||||||
|
</Field>
|
||||||
|
<Field name={"Фамилия"}>
|
||||||
|
<TextInput bind:value={current_item.sname}></TextInput>
|
||||||
|
</Field>
|
||||||
|
<Field name={"Отчество"}>
|
||||||
|
<TextInput bind:value={current_item.tname}></TextInput>
|
||||||
|
</Field>
|
||||||
|
{@const cur_cols = ["phone", "fname", "sname", "tname"]}
|
||||||
|
<TableCudButtons
|
||||||
|
{form}
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
on_add={async () => {
|
||||||
|
await sql_insert_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_save={async () => {
|
||||||
|
await sql_update_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_delete={sql_delete_short}
|
||||||
|
></TableCudButtons>
|
||||||
|
</Dialog>
|
||||||
|
<TableButtonsWrap
|
||||||
|
{current_table}
|
||||||
|
{current_item}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
on_add_click={() => {
|
||||||
|
current_item = {
|
||||||
|
id: 0,
|
||||||
|
phone: "+7 (",
|
||||||
|
fname: "",
|
||||||
|
sname: "",
|
||||||
|
tname: "",
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
columns={["phone", "fname", "sname", "tname"]}
|
||||||
|
let:is_searching
|
||||||
|
let:search_query_result
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
{is_searching}
|
||||||
|
search_query={db.select(search_query_result)}
|
||||||
|
columns={["id", "сотовый", "фио"]}
|
||||||
|
query={db.select(`select * from ${current_table}`)}
|
||||||
|
let:item
|
||||||
|
let:index
|
||||||
|
>
|
||||||
|
<Tr {index} on_click={() => open_item_edit(item)}>
|
||||||
|
<Atd {item} names={["id", "phone"]}></Atd>
|
||||||
|
<Td>{item.sname} {item.fname[0]}.{item.tname[0]}</Td>
|
||||||
|
</Tr>
|
||||||
|
</Table>
|
||||||
|
</TableButtonsWrap>
|
||||||
|
{:else if current_table === "Requests"}
|
||||||
|
<Dialog
|
||||||
|
bind:is_open={is_item_dialog_open}
|
||||||
|
name={cur_dialog_name}
|
||||||
|
let:form
|
||||||
|
>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<h1>id {current_item.id}</h1>
|
||||||
|
{/if}
|
||||||
|
<Field name={"Клиент"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.client_id}
|
||||||
|
query={db.select("select id, fname, sname, tname from Clients")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.sname}
|
||||||
|
{vtype.sname[0]}. {vtype.tname[0]}.
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
<Field name={"Ответсвтенный"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.employee_id}
|
||||||
|
query={db.select("select id, fname, sname, tname from Employees")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.sname}
|
||||||
|
{vtype.sname[0]}. {vtype.tname[0]}.
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<Field name={"Статус"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.request_status_id}
|
||||||
|
query={db.select("select id, name from RequestStatus")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.name}
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
{/if}
|
||||||
|
<Field name={"Тип запроса"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.request_type_id}
|
||||||
|
query={db.select("select id, name from RequestType")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.name}
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<h1>Дата начала {current_item.initial_date}</h1>
|
||||||
|
{/if}
|
||||||
|
<Field name={"Описание запроса"}>
|
||||||
|
<TextInput is_input={false} bind:value={current_item.description}
|
||||||
|
></TextInput>
|
||||||
|
</Field>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<Field name={"Время"}>
|
||||||
|
<NumberInput
|
||||||
|
is_required={false}
|
||||||
|
is_float={true}
|
||||||
|
bind:value={current_item.duration}
|
||||||
|
></NumberInput>
|
||||||
|
</Field>
|
||||||
|
{/if}
|
||||||
|
{@const cur_cols = [
|
||||||
|
"client_id",
|
||||||
|
"employee_id",
|
||||||
|
"request_status_id",
|
||||||
|
"request_type_id",
|
||||||
|
"initial_date",
|
||||||
|
"description",
|
||||||
|
"duration",
|
||||||
|
]}
|
||||||
|
|
||||||
|
<TableCudButtons
|
||||||
|
{form}
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
on_add={async () => {
|
||||||
|
await sql_insert_short(current_table, [
|
||||||
|
"client_id",
|
||||||
|
"employee_id",
|
||||||
|
"request_status_id",
|
||||||
|
"request_type_id",
|
||||||
|
"description",
|
||||||
|
"duration",
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
on_save={async () => {
|
||||||
|
await sql_update_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_delete={sql_delete_short}
|
||||||
|
></TableCudButtons>
|
||||||
|
</Dialog>
|
||||||
|
<TableButtonsWrap
|
||||||
|
{current_table}
|
||||||
|
{current_item}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
on_add_click={() => {
|
||||||
|
current_item = {
|
||||||
|
id: 0,
|
||||||
|
client_id: 0,
|
||||||
|
employee_id: 1,
|
||||||
|
request_status_id: 1,
|
||||||
|
request_type_id: 1,
|
||||||
|
description: "",
|
||||||
|
duration: null,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
"client_id",
|
||||||
|
"employee_id",
|
||||||
|
"request_status_id",
|
||||||
|
"request_type_id",
|
||||||
|
"initial_date",
|
||||||
|
"description",
|
||||||
|
"duration",
|
||||||
|
]}
|
||||||
|
ex_params={[
|
||||||
|
{
|
||||||
|
name: "Clients.fname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Clients.sname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Clients.tname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Employees.fname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Employees.sname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Employees.tname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RequestStatus.name",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RequestType.name",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
joins={`
|
||||||
|
JOIN Clients on Clients.id = Requests.client_id
|
||||||
|
JOIN Employees on Employees.id = Requests.employee_id
|
||||||
|
JOIN RequestStatus on RequestStatus.id = Requests.request_status_id
|
||||||
|
JOIN RequestType on RequestType.id = Requests.request_type_id
|
||||||
|
`}
|
||||||
|
let:is_searching
|
||||||
|
let:search_query_result
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
{is_searching}
|
||||||
|
search_query={db.select(search_query_result)}
|
||||||
|
columns={[
|
||||||
|
"id",
|
||||||
|
"клиент",
|
||||||
|
"ответсвенный",
|
||||||
|
"статус",
|
||||||
|
"тип",
|
||||||
|
"дата начала",
|
||||||
|
// "описание",
|
||||||
|
"длительность",
|
||||||
|
]}
|
||||||
|
query={db.select(`select * from ${current_table}`)}
|
||||||
|
let:item
|
||||||
|
let:index
|
||||||
|
>
|
||||||
|
<Tr {index} on_click={() => open_item_edit(item)}>
|
||||||
|
<Td>
|
||||||
|
{item.id}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select fname, sname, tname from Clients where id = ${item.client_id}`) then data}
|
||||||
|
{data[0].fname} {data[0].sname[0]}. {data[0].tname[0]}.
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select id, fname, sname, tname from Employees where id = ${item.employee_id}`) then data}
|
||||||
|
{data[0].fname} {data[0].sname[0]}. {data[0].tname[0]}.
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select id, name from RequestStatus where id = ${item.request_status_id}`) then data}
|
||||||
|
{data[0].name}
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select id, name from RequestType where id = ${item.request_type_id}`) then data}
|
||||||
|
{data[0].name}
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
<Atd {item} names={["initial_date", "duration"]}></Atd>
|
||||||
|
</Tr>
|
||||||
|
</Table>
|
||||||
|
</TableButtonsWrap>
|
||||||
|
{/if}
|
||||||
|
{:else if is_view_open}
|
||||||
|
{#if current_view === "RequestCount"}
|
||||||
|
<ViewTable
|
||||||
|
query={db.select(
|
||||||
|
`SELECT 'значение' AS name, COUNT(id) AS value FROM Requests;`,
|
||||||
|
)}
|
||||||
|
columns={["name", "aa"]}
|
||||||
|
names={["name", "value"]}
|
||||||
|
></ViewTable>
|
||||||
|
{:else if current_view === "AvgRequestTime"}
|
||||||
|
<ViewTable
|
||||||
|
query={db.select(
|
||||||
|
`SELECT 'значение' AS name, AVG(duration) AS value
|
||||||
|
FROM Requests
|
||||||
|
WHERE duration NOT NULL;`,
|
||||||
|
)}
|
||||||
|
columns={["name", "value"]}
|
||||||
|
names={["name", "value"]}
|
||||||
|
></ViewTable>
|
||||||
|
{:else if current_view === "StatByType"}
|
||||||
|
<ViewTable
|
||||||
|
query={db.select(`
|
||||||
|
SELECT
|
||||||
|
RequestType.name AS type_name,
|
||||||
|
AVG(duration) AS avg_duratino,
|
||||||
|
COUNT(request_type_id) as type_count
|
||||||
|
FROM Requests
|
||||||
|
JOIN RequestType on RequestType.id = Requests.request_type_id
|
||||||
|
WHERE duration
|
||||||
|
GROUP BY request_type_id;
|
||||||
|
`)}
|
||||||
|
columns={["название", "среднее время", "количество"]}
|
||||||
|
names={["type_name", "avg_duratino", "type_count"]}
|
||||||
|
></ViewTable>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if current_page === "Сотрудники"}
|
||||||
|
{#if current_table === "Employees"}
|
||||||
|
<Dialog
|
||||||
|
bind:is_open={is_item_dialog_open}
|
||||||
|
name={cur_dialog_name}
|
||||||
|
let:form
|
||||||
|
>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<h1>id {current_item.id}</h1>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Field name={"Должность"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.employee_status_id}
|
||||||
|
query={db.select("select id, name from EmployeeStatus")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.name}
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name={"Имя"}>
|
||||||
|
<TextInput bind:value={current_item.fname}></TextInput>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name={"Фамилия"}>
|
||||||
|
<TextInput bind:value={current_item.sname}></TextInput>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name={"Отчество"}>
|
||||||
|
<TextInput bind:value={current_item.tname}></TextInput>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
{@const cur_cols = ["employee_status_id", "fname", "sname", "tname"]}
|
||||||
|
|
||||||
|
<TableCudButtons
|
||||||
|
{form}
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
on_add={async () => {
|
||||||
|
await sql_insert_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_save={async () => {
|
||||||
|
await sql_update_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_delete={sql_delete_short}
|
||||||
|
></TableCudButtons>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<TableButtonsWrap
|
||||||
|
{current_table}
|
||||||
|
{current_item}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
on_add_click={() => {
|
||||||
|
current_item = {
|
||||||
|
id: 0,
|
||||||
|
employee_status_id: 1,
|
||||||
|
fname: "",
|
||||||
|
sname: "",
|
||||||
|
tname: "",
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
columns={["employee_status_id", "fname", "sname", "tname"]}
|
||||||
|
joins={`JOIN EmployeeStatus on EmployeeStatus.id = Employees.employee_status_id`}
|
||||||
|
ex_params={[
|
||||||
|
{
|
||||||
|
name: "EmployeeStatus.name",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
let:is_searching
|
||||||
|
let:search_query_result
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
{is_searching}
|
||||||
|
search_query={db.select(search_query_result)}
|
||||||
|
columns={["id", "должность", "фио"]}
|
||||||
|
query={db.select(`select * from ${current_table}`)}
|
||||||
|
let:item
|
||||||
|
let:index
|
||||||
|
>
|
||||||
|
<Tr {index} on_click={() => open_item_edit(item)}>
|
||||||
|
<Atd {item} names={["id"]}></Atd>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select name from EmployeeStatus where id = ${item.employee_status_id}`) then data}
|
||||||
|
{data[0].name}
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
<Td>{item.sname} {item.fname[0]}.{item.tname[0]}</Td>
|
||||||
|
</Tr>
|
||||||
|
</Table>
|
||||||
|
</TableButtonsWrap>
|
||||||
|
{:else if current_table === "Accounts"}
|
||||||
|
<Dialog
|
||||||
|
bind:is_open={is_item_dialog_open}
|
||||||
|
name={cur_dialog_name}
|
||||||
|
let:form
|
||||||
|
>
|
||||||
|
{#if !is_dialog_item_add}
|
||||||
|
<h1>id {current_item.id}</h1>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Field name={"Сотрудник"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.employee_id}
|
||||||
|
query={db.select("select id, fname, sname, tname from Employees")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.fname}
|
||||||
|
{vtype.sname[0]}. {vtype.tname[0]}.
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name={"Доступ"}>
|
||||||
|
<Select
|
||||||
|
bind:value={current_item.account_access_level_id}
|
||||||
|
query={db.select("select id, name from AccountAccessLevel")}
|
||||||
|
let:vtype
|
||||||
|
>
|
||||||
|
{vtype.id}
|
||||||
|
{vtype.name}
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name={"login"}>
|
||||||
|
<TextInput bind:value={current_item.login}></TextInput>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name={"Пароль"}>
|
||||||
|
<TextInput bind:value={current_item.password}></TextInput>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
{@const cur_cols = [
|
||||||
|
"employee_id",
|
||||||
|
"account_access_level_id",
|
||||||
|
"login",
|
||||||
|
"password",
|
||||||
|
]}
|
||||||
|
|
||||||
|
<TableCudButtons
|
||||||
|
{form}
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
on_add={async () => {
|
||||||
|
await sql_insert_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_save={async () => {
|
||||||
|
await sql_update_short(current_table, cur_cols);
|
||||||
|
}}
|
||||||
|
on_delete={sql_delete_short}
|
||||||
|
></TableCudButtons>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<TableButtonsWrap
|
||||||
|
{current_table}
|
||||||
|
{current_item}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
on_add_click={() => {
|
||||||
|
current_item = {
|
||||||
|
id: 0,
|
||||||
|
employee_id: 0,
|
||||||
|
account_access_level_id: 0,
|
||||||
|
login: "",
|
||||||
|
password: "",
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
"employee_id",
|
||||||
|
"account_access_level_id",
|
||||||
|
"login",
|
||||||
|
"password",
|
||||||
|
]}
|
||||||
|
joins={"JOIN Employees on Accounts.employee_id = Employees.id"}
|
||||||
|
ex_params={[
|
||||||
|
{
|
||||||
|
name: "Employees.fname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Employees.sname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Employees.tname",
|
||||||
|
is_number: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
let:is_searching
|
||||||
|
let:search_query_result
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
{is_searching}
|
||||||
|
search_query={db.select(search_query_result)}
|
||||||
|
columns={["id", "Сотрудник", "Доступ", "Логин"]}
|
||||||
|
query={db.select(`select * from ${current_table}`)}
|
||||||
|
let:item
|
||||||
|
let:index
|
||||||
|
>
|
||||||
|
<Tr {index} on_click={() => open_item_edit(item)}>
|
||||||
|
<Atd {item} names={["id"]}></Atd>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select id, fname, sname, tname from Employees where id = ${item.employee_id}`) then data}
|
||||||
|
{data[0].fname} {data[0].sname[0]}. {data[0].tname[0]}.
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{#await db.select(`select name from AccountAccessLevel where id = ${item.account_access_level_id}`) then data}
|
||||||
|
{data[0].name}
|
||||||
|
{/await}
|
||||||
|
</Td>
|
||||||
|
<Atd {item} names={["login"]}></Atd>
|
||||||
|
</Tr>
|
||||||
|
</Table>
|
||||||
|
</TableButtonsWrap>
|
||||||
|
{:else if current_table === "AccountAccessLevel"}
|
||||||
|
<TableListShort
|
||||||
|
{db}
|
||||||
|
{cur_dialog_name}
|
||||||
|
{current_table}
|
||||||
|
bind:is_item_dialog_open
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
bind:current_item
|
||||||
|
query={db.select(`select * from ${current_table}`)}
|
||||||
|
columns={["id", "name"]}
|
||||||
|
columns_display={["id", "название"]}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
{open_item_edit}
|
||||||
|
{sql_insert_short}
|
||||||
|
{sql_update_short}
|
||||||
|
{sql_delete_short}
|
||||||
|
></TableListShort>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !is_view_open && current_page === "Списки"}
|
||||||
|
{#if ["RequestStatus", "RequestType", "EmployeeStatus"].includes(current_table)}
|
||||||
|
<TableListShort
|
||||||
|
{db}
|
||||||
|
{cur_dialog_name}
|
||||||
|
{current_table}
|
||||||
|
bind:is_item_dialog_open
|
||||||
|
bind:is_dialog_item_add
|
||||||
|
bind:current_item
|
||||||
|
query={db.select(`select * from ${current_table}`)}
|
||||||
|
columns={["id", "name"]}
|
||||||
|
columns_display={["id", "название"]}
|
||||||
|
{toggle_add_dialog}
|
||||||
|
{open_item_edit}
|
||||||
|
{sql_insert_short}
|
||||||
|
{sql_update_short}
|
||||||
|
{sql_delete_short}
|
||||||
|
></TableListShort>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</ScreenWrap>
|
BIN
static/jbmv.ttf
Normal file
13
svelte.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import adapter from "@sveltejs/adapter-static";
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
kit: {
|
||||||
|
adapter: adapter(),
|
||||||
|
},
|
||||||
|
preprocess: vitePreprocess()
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
10
tailwind.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
|
darkMode: 'selector',
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
32
vite.config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
|
|
||||||
|
// @ts-expect-error process is a nodejs global
|
||||||
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(async () => ({
|
||||||
|
plugins: [sveltekit()],
|
||||||
|
|
||||||
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
|
//
|
||||||
|
// 1. prevent vite from obscuring rust errors
|
||||||
|
clearScreen: false,
|
||||||
|
// 2. tauri expects a fixed port, fail if that port is not available
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
host: host || false,
|
||||||
|
hmr: host
|
||||||
|
? {
|
||||||
|
protocol: "ws",
|
||||||
|
host,
|
||||||
|
port: 1421,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
watch: {
|
||||||
|
// 3. tell vite to ignore watching `src-tauri`
|
||||||
|
ignored: ["**/src-tauri/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|