management of library
All checks were successful
build / windows (push) Successful in 2m38s
build / linux (push) Successful in 2m0s

This commit is contained in:
2025-03-07 11:51:21 +01:00
parent e90cdf4e6a
commit 5bedea5312
26 changed files with 1078 additions and 228 deletions

View File

@ -1,25 +1,37 @@
<script lang="ts">
import "./app.css";
import { Router, Route, Link, navigate } from "svelte-routing";
import Home from "./routes/Home.svelte";
import "./app.css";
import { Navbar, DarkMode, Heading } from "flowbite-svelte";
import { HomeOutline } from "flowbite-svelte-icons";
import Books from "./routes/Books.svelte";
import Clients from "./routes/Clients.svelte";
import Lendings from "./routes/Lendings.svelte";
let url: string = $state("/");
</script>
<main class="flex-col h-screen items-center bg-gray-50 dark:bg-gray-900">
<Router bind:url>
<Navbar class="border-b">
<button
class="grid grid-cols-3 items-center"
onclick={() => navigate("/")}
>
<HomeOutline />
<span class="col-span-2">HOME</span>
</button>
<div class="flex gap-5">
<button
class="grid grid-cols-3 items-center"
onclick={() => navigate("/")}
>
<HomeOutline />
<span class="col-span-2">HOME</span>
</button>
<Link to="/clients">Clients</Link>
<Link to="/books">Books</Link>
</div>
<DarkMode />
</Navbar>
<Route path="/"><Home /></Route>
<Route path="/"><Lendings clientId={null} /></Route>
<Route path="/books"><Books /></Route>
<Route path="/clients"><Clients /></Route>
<Route path="/clients/:id" let:params>
<Lendings clientId={parseInt(params.id)} />
</Route>
</Router>
</main>

View File

@ -0,0 +1,24 @@
<script lang="ts">
import { Button, Input, Label } from "flowbite-svelte";
import { model } from "../../wailsjs/go/models";
let {
author = new model.Author(),
onsubmit,
}: { author: model.Author; onsubmit: (a: model.Author) => void } = $props();
function submit(e: Event) {
e.preventDefault();
onsubmit(author);
}
</script>
<form onsubmit={submit}>
<div class="m-5">
<Label>Name</Label>
<Input type="text" bind:value={author.Name} />
</div>
<div class="m-5">
<Button type="submit">Save</Button>
</div>
</form>

View File

@ -0,0 +1,73 @@
<script lang="ts">
import { Button, Input, Label, Modal, Select } from "flowbite-svelte";
import { model } from "../../wailsjs/go/models";
import { GetAuthors, SaveAuthor } from "../../wailsjs/go/main/App";
import AuthorEditor from "./AuthorEditor.svelte";
import { onMount } from "svelte";
const ISBNRegex =
"^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)97[89][- ]?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9]$";
let {
book = $bindable(),
onsubmit = (_) => {},
}: { book: model.Book; onsubmit: (a: model.Book) => void } = $props();
let authors: model.Author[] = $state([]);
let modal: boolean = $state(false);
let newAuthor: model.Author = $state();
function update() {
GetAuthors().then((as) => (authors = as));
}
function submit(e: Event) {
e.preventDefault();
onsubmit(book);
}
onMount(update);
</script>
<Modal bind:open={modal}>
<AuthorEditor
author={newAuthor}
onsubmit={(a) => {
SaveAuthor(a).then(update);
modal = false;
}}
/>
</Modal>
<form onsubmit={submit}>
<div class="m-5">
<Label>Name</Label>
<Input type="text" bind:value={book.Title} />
</div>
<div class="m-5">
<Label>ISBN</Label>
<Input pattern={ISBNRegex} type="text" bind:value={book.ISBN} />
</div>
<div class="m-5">
<Label>Author</Label>
<div class="grid grid-cols-5 gap-5">
<Select
class="col-span-4"
items={authors.map((a) => {
return { value: a.ID, name: a.Name };
})}
bind:value={book.AuthorID}
/>
<Button
onclick={() => {
newAuthor = new model.Author();
modal = true;
}}>New</Button
>
</div>
</div>
<div class="m-5">
<Button type="submit">Save</Button>
</div>
</form>

View File

@ -0,0 +1,30 @@
<script lang="ts">
import { Button, Input, Label } from "flowbite-svelte";
import { model } from "../../wailsjs/go/models";
let {
client = $bindable(),
onsubmit = (_) => {},
}: { client: model.Client; onsubmit: (a: model.Client) => void } = $props();
function submit(e: Event) {
e.preventDefault();
onsubmit(client);
}
</script>
<form onsubmit={submit}>
<div class="m-5">
<Label>Name</Label>
<Input type="text" bind:value={client.Name} />
</div>
<div class="m-5">
<Label>ISBN</Label>
<Input type="email" bind:value={client.Email} />
</div>
<div class="m-5">
<Button type="submit">Save</Button>
</div>
</form>

View File

@ -0,0 +1,73 @@
<script lang="ts">
import {
Button,
Checkbox,
Input,
Label,
Modal,
Select,
} from "flowbite-svelte";
import { model } from "../../wailsjs/go/models";
import {
GetAuthors,
GetAvailableBooks,
GetClients,
SaveAuthor,
} from "../../wailsjs/go/main/App";
import AuthorEditor from "./AuthorEditor.svelte";
import { onMount } from "svelte";
import TimeInput from "./TimeInput.svelte";
let {
lending = $bindable(),
onsubmit = (_) => {},
}: { lending: model.Lending; onsubmit: (l: model.Lending) => void } =
$props();
let books: model.Book[] = $state([]);
let clients: model.Client[] = $state([]);
function update() {
GetAvailableBooks().then((bs) => (books = bs));
GetClients().then((cl) => (clients = cl));
}
function submit(e: Event) {
e.preventDefault();
onsubmit(lending);
}
onMount(update);
</script>
<form onsubmit={submit}>
<div class="m-5">
<Label>Book</Label>
<Select
class="col-span-4"
items={books.map((b) => {
return { value: b.ID, name: b.Title };
})}
bind:value={lending.BookID}
/>
</div>
<div class="m-5">
<Label>Client</Label>
<Select
class="col-span-4"
items={clients.map((c) => {
return { value: c.ID, name: `${c.Email} ~ ${c.Name}` };
})}
bind:value={lending.ClientID}
/>
</div>
<div class="m-5">
<Label>Due Date</Label>
<TimeInput bind:value={lending.DueDate} />
</div>
<div class="m-5">
<Checkbox bind:checked={lending.Returned}>Returned</Checkbox>
</div>
<div class="m-5">
<Button type="submit">Save</Button>
</div>
</form>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Input } from "flowbite-svelte";
let { value }: { value: Date } = $props();
let { value = $bindable() }: { value: Date } = $props();
const formatDateTimeLocal = (date: Date) => {
const pad = (num) => num.toString().padStart(2, "0");

View File

@ -0,0 +1,24 @@
<script lang="ts">
import { Button, Input, Label } from "flowbite-svelte";
import { model } from "../../wailsjs/go/models";
let {
author = $bindable(),
onsubmit = (_) => {},
}: { author: model.Author; onsubmit: (a: model.Author) => void } = $props();
function submit(e: Event) {
e.preventDefault();
onsubmit(author);
}
</script>
<form onsubmit={submit}>
<div class="m-5">
<Label>Name</Label>
<Input type="text" bind:value={author.Name} />
</div>
<div class="m-5">
<Button type="submit">Save</Button>
</div>
</form>

View File

@ -0,0 +1,118 @@
<script lang="ts">
import {
Badge,
Button,
Modal,
Spinner,
Table,
TableBody,
TableBodyCell,
TableBodyRow,
TableHead,
TableHeadCell,
} from "flowbite-svelte";
import BookEditor from "../components/BookEditor.svelte";
import { onMount } from "svelte";
import {
GetBooks,
SaveBook,
BookLended,
SaveLending,
} from "../../wailsjs/go/main/App";
import { model } from "../../wailsjs/go/models";
import LendingEditor from "../components/LendingEditor.svelte";
let books: model.Book[] = $state();
let book: model.Book | null = $state(null);
let lending: model.Lending | null = $state(null);
let modal: boolean = $state(false);
function update() {
GetBooks().then((bs) => (books = bs));
}
onMount(update);
</script>
{#if book}
<Modal bind:open={modal} title="Book">
<BookEditor
bind:book
onsubmit={(b) => {
SaveBook(b).then(update);
modal = false;
book = null;
}}
/>
</Modal>
{:else if lending}
<Modal bind:open={modal} title="New Lending">
<LendingEditor
bind:lending
onsubmit={(l) => {
SaveLending(l).then(update);
modal = false;
lending = null;
}}
/>
</Modal>
{/if}
<Table>
<TableHead>
<TableHeadCell>Title</TableHeadCell>
<TableHeadCell>Author</TableHeadCell>
<TableHeadCell>ISBN</TableHeadCell>
<TableHeadCell>Status</TableHeadCell>
<TableHeadCell>
<Button
onclick={() => {
book = new model.Book();
modal = true;
}}>New</Button
></TableHeadCell
>
</TableHead>
<TableBody>
{#each books as b}
<TableBodyRow>
<TableBodyCell>{b.Title}</TableBodyCell>
<TableBodyCell>{b.Author.Name}</TableBodyCell>
<TableBodyCell>{b.ISBN}</TableBodyCell>
<TableBodyCell
>{#await BookLended(b.ID)}
<Spinner />
{:then lended}
{#if lended}
<Badge color="red">Lended</Badge>
{:else}
<Badge class="text-sm" color="green">Available</Badge>
{/if}
{/await}</TableBodyCell
>
<TableBodyCell>
{#await BookLended(b.ID) then lended}
{#if !lended}
<Button
onclick={() => {
lending = new model.Lending();
lending.BookID = b.ID;
lending.DueDate = new Date();
modal = true;
}}>Lend</Button
>
{/if}
{/await}
<Button
onclick={() => {
book = b;
modal = true;
}}>Edit</Button
>
</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>

View File

@ -0,0 +1,107 @@
<script lang="ts">
import {
Button,
Modal,
Table,
TableBody,
TableBodyCell,
TableBodyRow,
TableHead,
TableHeadCell,
} from "flowbite-svelte";
import ClientEditor from "../components/ClientEditor.svelte";
import { onMount } from "svelte";
import {
GetClients,
SaveClient,
SaveLending,
} from "../../wailsjs/go/main/App";
import { model } from "../../wailsjs/go/models";
import LendingEditor from "../components/LendingEditor.svelte";
import { navigate } from "svelte-routing";
let clients: model.Client[] = $state();
let client: model.Client | null = $state(null);
let lending: model.Lending | null = $state(null);
let modal: boolean = $state(false);
function update() {
GetClients().then((cs) => (clients = cs));
}
onMount(update);
</script>
{#if client}
<Modal bind:open={modal} title="Client">
<ClientEditor
{client}
onsubmit={(c) => {
SaveClient(c).then(update);
modal = false;
}}
/>
</Modal>{:else if lending}
<Modal bind:open={modal} title="New Lending">
<LendingEditor
bind:lending
onsubmit={(l) => {
SaveLending(l).then(update);
modal = false;
lending = null;
}}
/>
</Modal>
{/if}
<Table>
<TableHead>
<TableHeadCell>Name</TableHeadCell>
<TableHeadCell>Email</TableHeadCell>
<TableHeadCell>Active Lendings</TableHeadCell>
<TableHeadCell>Overdue Lendings</TableHeadCell>
<TableHeadCell>
<Button
onclick={() => {
client = new model.Client();
modal = true;
}}>New</Button
></TableHeadCell
>
</TableHead>
<TableBody>
{#each clients as c}
<TableBodyRow>
<TableBodyCell>{c.Name}</TableBodyCell>
<TableBodyCell>{c.Email}</TableBodyCell>
<TableBodyCell
>{c.Lendings.filter((l) => {
return !l.Returned;
}).length}
</TableBodyCell>
<TableBodyCell
>{c.Lendings.filter((l) => {
return new Date(l.DueDate).getTime() > Date.now();
}).length}
</TableBodyCell>
<TableBodyCell>
<Button onclick={() => navigate(`/clients/${c.ID}`)}>View</Button>
<Button
onclick={() => {
lending = new model.Lending();
lending.DueDate = new Date();
lending.ClientID = c.ID;
modal = true;
}}>Lend a Book</Button
>
<Button
onclick={() => {
client = c;
modal = true;
}}>Edit</Button
>
</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>

View File

@ -1,74 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
import {
GetThings,
DeleteThing,
NewThing,
} from "../../wailsjs/go/things/Service";
import { model } from "../../wailsjs/go/models";
import {
Label,
Input,
Button,
Table,
TableHead,
TableHeadCell,
TableBody,
TableBodyRow,
TableBodyCell,
} from "flowbite-svelte";
let name: string = $state();
let thingsList: model.Thing[] = $state([]);
function update() {
GetThings().then((ts) => {
thingsList = ts;
});
}
function submit(e: Event) {
e.preventDefault();
NewThing(name).then(update);
name = "";
}
function deleteEvent(id: number) {
DeleteThing(id).then(update);
}
onMount(update);
</script>
<form class="max-w-96 m-5 grid-cols-1 gap-10" onsubmit={submit}>
<div class="m-5">
<Label for="first_name" class="mb-2">First name</Label>
<Input type="text" placeholder="John" bind:value={name} required />
</div>
<div class="m-5">
<Button type="submit">Submit</Button>
</div>
</form>
<Table>
<TableHead>
<TableHeadCell>ID</TableHeadCell>
<TableHeadCell>Name</TableHeadCell>
<TableHeadCell>Delete</TableHeadCell>
</TableHead>
<TableBody>
{#each thingsList as t}
<TableBodyRow>
<TableBodyCell>
{t.ID}
</TableBodyCell>
<TableBodyCell>
{t.Name}
</TableBodyCell>
<TableBodyCell>
<Button on:click={(_) => deleteEvent(t.ID)}>Delete</Button>
</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>

View File

@ -0,0 +1,188 @@
<script lang="ts">
import {
GetReturnedLendings,
GetLendings,
ReturnLending,
SaveLending,
DeleteLending,
} from "../../wailsjs/go/main/App";
import { model } from "../../wailsjs/go/models";
import { BrowserOpenURL } from "../../wailsjs/runtime/runtime";
import { onMount } from "svelte";
import {
Button,
Heading,
Modal,
P,
Timeline,
TimelineItem,
} from "flowbite-svelte";
import LendingEditor from "../components/LendingEditor.svelte";
let { clientId = null }: { clientId: number | null } = $props();
let lendings: model.Lending[] = $state([]);
let returnedLendings: model.Lending[] = $state([]);
let lending: model.Lending = $state();
let modal: boolean = $state(false);
function update() {
GetLendings().then((ls) => {
lendings = ls.filter((l) => {
if (clientId && l.ClientID !== clientId) {
return false;
}
return true;
});
});
GetReturnedLendings().then((ls) => (returnedLendings = ls.filter((l) => {
if (clientId && l.ClientID !== clientId) {
return false;
}
return true;
})));
}
onMount(update);
//
</script>
<Modal bind:open={modal} title="Lending">
<LendingEditor
bind:lending
onsubmit={(l) => {
SaveLending(l).then(update);
modal = false;
lending = null;
}}
/>
</Modal>
<div class="m-5 grid grid-cols-5">
<Heading tag="h1" class="col-span-4">Lendings</Heading>
<Button
onclick={() => {
lending = new model.Lending();
lending.DueDate = new Date();
if (clientId) {
lending.ClientID = clientId
}
modal = true;
}}>New</Button
>
</div>
<section class="m-5 mb-20">
<Heading tag="h2" class="mb-5">Overdue</Heading>
{#if lendings.filter((l) => {
return new Date(l.DueDate).getTime() < Date.now();
}).length == 0}
<P>No Entries</P>
{/if}
<Timeline>
{#each lendings.filter((l) => {
return new Date(l.DueDate).getTime() < Date.now();
}) as l}
<TimelineItem
title={`"${l.Book.Title}" is lended to ${l.Client.Name}`}
date={new Date(l.DueDate).toLocaleString()}
>
<P>
The book "{l.Book.Title}" (ISBN: {l.Book.ISBN}) is lended to {l.Client
.Name} (<button
class="underline"
onclick={() => BrowserOpenURL(`mailto:${l.Client.Email}`)}
>{l.Client.Email}</button
>)
</P>
<div class="flex gap-5 my-5">
<Button
onclick={() => {
lending = l;
modal = true;
}}>Edit</Button
>
<Button
color="red"
onclick={() => {
ReturnLending(l).then(update);
}}>Set to Returned</Button
>
</div>
</TimelineItem>
{/each}
</Timeline>
</section>
<section class="m-5 mb-20">
<Heading tag="h2" class="mb-5">Active</Heading>
{#if lendings.filter((l) => {
return new Date(l.DueDate).getTime() > Date.now();
}).length == 0}
<P>No Entries</P>
{/if}
<Timeline>
{#each lendings.filter((l) => {
return new Date(l.DueDate).getTime() > Date.now();
}) as l}
<TimelineItem
title={`"${l.Book.Title}" is lended to ${l.Client.Name}`}
date={new Date(l.DueDate).toLocaleString()}
>
<P>
The book "{l.Book.Title}" (ISBN: {l.Book.ISBN}) is lended to {l.Client
.Name} (<button
class="underline"
onclick={() => BrowserOpenURL(`mailto:${l.Client.Email}`)}
>{l.Client.Email}</button
>)
</P>
<div class="flex gap-5 my-5">
<Button
onclick={() => {
lending = l;
modal = true;
}}>Edit</Button
>
<Button
color="red"
onclick={() => {
ReturnLending(l).then(update);
}}>Set to Returned</Button
>
</div>
</TimelineItem>
{/each}
</Timeline>
</section>
<section class="m-5 mb-20">
<Heading tag="h2" class="mb-5">Returned</Heading>
{#if returnedLendings.length == 0}
<P>No Entries</P>
{/if}
<Timeline>
{#each returnedLendings as l}
<TimelineItem
title={`"${l.Book.Title}" was lended to ${l.Client.Name}`}
date={new Date(l.DueDate).toLocaleString()}
>
<P>
The book "{l.Book.Title}" (ISBN: {l.Book.ISBN}) was lended to {l
.Client.Name} (<button
class="underline"
onclick={() => BrowserOpenURL(`mailto:${l.Client.Email}`)}
>{l.Client.Email}</button
>)
</P>
<div class="flex gap-5 my-5">
<Button
color="red"
onclick={() => {
DeleteLending(l).then(update);
}}>Delete</Button
>
</div>
</TimelineItem>
{/each}
</Timeline>
</section>