generated from schreifuchs/wails-template
	management of library
This commit is contained in:
		
							
								
								
									
										3
									
								
								.timer.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.timer.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## dates and their corresponding seconds been here :) | ||||
| [25-03-07] | ||||
| schreifuchs_at_archibald = 407 | ||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,12 +1,8 @@ | ||||
| # wails template | ||||
| This is my wails-template i am using for the *ICT-Regiomeisterschaften 2025*.  | ||||
| It uses vanilla svelte with vite in the Frontend. For styling i use Flowbyte and TailwindCSS. I have setup a pipeline to build windows and linux binaries. For windows i have a working installer. | ||||
|  | ||||
| Feel free to use this template on your own. To set it up just run ```setup.sh```. If you're not using gitea you will have to rename the ```.gitea``` folder to ```.github```, to use the pipelines.  | ||||
|  | ||||
| Enjoy :) | ||||
| # Library Manager | ||||
|  | ||||
| übung für ICT-Regios 2025 | ||||
|  | ||||
| zeit: 4h 15min | ||||
|  | ||||
| ## Links | ||||
|  | ||||
|   | ||||
							
								
								
									
										101
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								app.go
									
									
									
									
									
								
							| @@ -3,19 +3,22 @@ package main | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"library-manager/model" | ||||
|  | ||||
| 	"github.com/gen2brain/beeep" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // App struct | ||||
| type App struct { | ||||
| 	ctx context.Context | ||||
| 	db  *gorm.DB | ||||
| } | ||||
|  | ||||
| // NewApp creates a new App application struct | ||||
| func NewApp() *App { | ||||
| func NewApp(db *gorm.DB) *App { | ||||
|  | ||||
| 	return &App{} | ||||
| 	return &App{db: db} | ||||
| } | ||||
|  | ||||
| // startup is called when the app starts. The context is saved | ||||
| @@ -33,3 +36,97 @@ func (a *App) startup(ctx context.Context) { | ||||
| func (a *App) Greet(name string) string { | ||||
| 	return fmt.Sprintf("Hello %s, It's show time!", name) | ||||
| } | ||||
|  | ||||
| // Authors CRIUD | ||||
| func (a *App) SaveAuthor(au *model.Author) { | ||||
| 	a.db.Save(au) | ||||
| } | ||||
|  | ||||
| func (a *App) DeleteAuthor(au *model.Author) { | ||||
| 	a.db.Delete(au) | ||||
| } | ||||
|  | ||||
| func (a *App) GetAuthors() (authors []model.Author) { | ||||
| 	a.db.Find(&authors) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (a *App) SaveBook(au *model.Book) { | ||||
| 	a.db.Save(au) | ||||
| } | ||||
|  | ||||
| func (a *App) DeleteBook(au *model.Book) { | ||||
| 	a.db.Delete(au) | ||||
| } | ||||
|  | ||||
| func (a *App) GetBooks() (authors []model.Book) { | ||||
| 	a.db.Preload("Author").Preload("Lendings").Find(&authors) | ||||
| 	return | ||||
| } | ||||
| func (a *App) GetAvailableBooks() (books []model.Book) { | ||||
| 	var bs []model.Book | ||||
| 	a.db.Preload("Author").Preload("Lendings").Find(&bs) | ||||
| 	books = make([]model.Book, 0, len(bs)) | ||||
|  | ||||
| 	for _, b := range bs { | ||||
| 		if !a.BookLended(b.ID) { | ||||
| 			books = append(books, b) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (a *App) BookLended(bId uint) bool { | ||||
| 	var lendings []model.Lending | ||||
| 	a.db.Where("book_id = ?", bId).Where("returned = FALSE").Find(&lendings) | ||||
|  | ||||
| 	fmt.Println(lendings) | ||||
|  | ||||
| 	return len(lendings) != 0 | ||||
| } | ||||
|  | ||||
| func (a *App) SaveClient(au *model.Client) { | ||||
| 	a.db.Save(au) | ||||
| } | ||||
|  | ||||
| func (a *App) DeleteClient(au *model.Client) { | ||||
| 	a.db.Delete(au) | ||||
| } | ||||
|  | ||||
| func (a *App) GetClients() (authors []model.Client) { | ||||
| 	a.db.Preload("Lendings").Find(&authors) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (a *App) SaveLending(au *model.Lending) string { | ||||
| 	if a.BookLended(Greater(au.BookID, au.Book.ID)) { | ||||
| 		return "Book is not available" | ||||
| 	} | ||||
| 	a.db.Save(au) | ||||
|  | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (a *App) ReturnLending(l *model.Lending) { | ||||
| 	l.Returned = true | ||||
| 	a.db.Save(l) | ||||
|  | ||||
| } | ||||
|  | ||||
| func (a *App) DeleteLending(au *model.Lending) { | ||||
| 	a.db.Delete(au) | ||||
| } | ||||
|  | ||||
| func (a *App) GetLendings() (lendings []model.Lending) { | ||||
| 	a.db.Preload("Client").Preload("Book").Where("returned = FALSE").Find(&lendings) | ||||
| 	return | ||||
| } | ||||
| func (a *App) GetReturnedLendings() (lendings []model.Lending) { | ||||
| 	a.db.Preload("Client").Preload("Book").Where("returned = TRUE").Find(&lendings) | ||||
| 	return | ||||
| } | ||||
| func (a *App) GetAllLendings() (lendings []model.Lending) { | ||||
| 	a.db.Preload("Client").Preload("Book").Find(&lendings) | ||||
| 	return | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								frontend/.timer.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								frontend/.timer.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## dates and their corresponding seconds been here :) | ||||
| [25-03-07] | ||||
| schreifuchs_at_archibald = 15333 | ||||
| @@ -1,16 +1,19 @@ | ||||
| <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"> | ||||
|       <div class="flex gap-5"> | ||||
|         <button | ||||
|           class="grid grid-cols-3 items-center" | ||||
|           onclick={() => navigate("/")} | ||||
| @@ -18,8 +21,17 @@ | ||||
|           <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> | ||||
|   | ||||
							
								
								
									
										24
									
								
								frontend/src/components/AuthorEditor.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/src/components/AuthorEditor.svelte
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										73
									
								
								frontend/src/components/BookEditor.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								frontend/src/components/BookEditor.svelte
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										30
									
								
								frontend/src/components/ClientEditor.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								frontend/src/components/ClientEditor.svelte
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										73
									
								
								frontend/src/components/LendingEditor.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								frontend/src/components/LendingEditor.svelte
									
									
									
									
									
										Normal 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> | ||||
| @@ -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"); | ||||
|   | ||||
							
								
								
									
										24
									
								
								frontend/src/routes/AuthorEditor.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/src/routes/AuthorEditor.svelte
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										118
									
								
								frontend/src/routes/Books.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								frontend/src/routes/Books.svelte
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										107
									
								
								frontend/src/routes/Clients.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								frontend/src/routes/Clients.svelte
									
									
									
									
									
										Normal 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> | ||||
| @@ -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> | ||||
							
								
								
									
										188
									
								
								frontend/src/routes/Lendings.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								frontend/src/routes/Lendings.svelte
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										35
									
								
								frontend/wailsjs/go/main/App.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								frontend/wailsjs/go/main/App.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,39 @@ | ||||
| // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL | ||||
| // This file is automatically generated. DO NOT EDIT | ||||
| import {model} from '../models'; | ||||
|  | ||||
| export function BookLended(arg1:number):Promise<boolean>; | ||||
|  | ||||
| export function DeleteAuthor(arg1:model.Author):Promise<void>; | ||||
|  | ||||
| export function DeleteBook(arg1:model.Book):Promise<void>; | ||||
|  | ||||
| export function DeleteClient(arg1:model.Client):Promise<void>; | ||||
|  | ||||
| export function DeleteLending(arg1:model.Lending):Promise<void>; | ||||
|  | ||||
| export function GetAllLendings():Promise<Array<model.Lending>>; | ||||
|  | ||||
| export function GetAuthors():Promise<Array<model.Author>>; | ||||
|  | ||||
| export function GetAvailableBooks():Promise<Array<model.Book>>; | ||||
|  | ||||
| export function GetBooks():Promise<Array<model.Book>>; | ||||
|  | ||||
| export function GetClients():Promise<Array<model.Client>>; | ||||
|  | ||||
| export function GetLendings():Promise<Array<model.Lending>>; | ||||
|  | ||||
| export function GetReturnedLendings():Promise<Array<model.Lending>>; | ||||
|  | ||||
| export function Greet(arg1:string):Promise<string>; | ||||
|  | ||||
| export function ReturnLending(arg1:model.Lending):Promise<void>; | ||||
|  | ||||
| export function SaveAuthor(arg1:model.Author):Promise<void>; | ||||
|  | ||||
| export function SaveBook(arg1:model.Book):Promise<void>; | ||||
|  | ||||
| export function SaveClient(arg1:model.Client):Promise<void>; | ||||
|  | ||||
| export function SaveLending(arg1:model.Lending):Promise<string>; | ||||
|   | ||||
| @@ -2,6 +2,74 @@ | ||||
| // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL | ||||
| // This file is automatically generated. DO NOT EDIT | ||||
|  | ||||
| export function BookLended(arg1) { | ||||
|   return window['go']['main']['App']['BookLended'](arg1); | ||||
| } | ||||
|  | ||||
| export function DeleteAuthor(arg1) { | ||||
|   return window['go']['main']['App']['DeleteAuthor'](arg1); | ||||
| } | ||||
|  | ||||
| export function DeleteBook(arg1) { | ||||
|   return window['go']['main']['App']['DeleteBook'](arg1); | ||||
| } | ||||
|  | ||||
| export function DeleteClient(arg1) { | ||||
|   return window['go']['main']['App']['DeleteClient'](arg1); | ||||
| } | ||||
|  | ||||
| export function DeleteLending(arg1) { | ||||
|   return window['go']['main']['App']['DeleteLending'](arg1); | ||||
| } | ||||
|  | ||||
| export function GetAllLendings() { | ||||
|   return window['go']['main']['App']['GetAllLendings'](); | ||||
| } | ||||
|  | ||||
| export function GetAuthors() { | ||||
|   return window['go']['main']['App']['GetAuthors'](); | ||||
| } | ||||
|  | ||||
| export function GetAvailableBooks() { | ||||
|   return window['go']['main']['App']['GetAvailableBooks'](); | ||||
| } | ||||
|  | ||||
| export function GetBooks() { | ||||
|   return window['go']['main']['App']['GetBooks'](); | ||||
| } | ||||
|  | ||||
| export function GetClients() { | ||||
|   return window['go']['main']['App']['GetClients'](); | ||||
| } | ||||
|  | ||||
| export function GetLendings() { | ||||
|   return window['go']['main']['App']['GetLendings'](); | ||||
| } | ||||
|  | ||||
| export function GetReturnedLendings() { | ||||
|   return window['go']['main']['App']['GetReturnedLendings'](); | ||||
| } | ||||
|  | ||||
| export function Greet(arg1) { | ||||
|   return window['go']['main']['App']['Greet'](arg1); | ||||
| } | ||||
|  | ||||
| export function ReturnLending(arg1) { | ||||
|   return window['go']['main']['App']['ReturnLending'](arg1); | ||||
| } | ||||
|  | ||||
| export function SaveAuthor(arg1) { | ||||
|   return window['go']['main']['App']['SaveAuthor'](arg1); | ||||
| } | ||||
|  | ||||
| export function SaveBook(arg1) { | ||||
|   return window['go']['main']['App']['SaveBook'](arg1); | ||||
| } | ||||
|  | ||||
| export function SaveClient(arg1) { | ||||
|   return window['go']['main']['App']['SaveClient'](arg1); | ||||
| } | ||||
|  | ||||
| export function SaveLending(arg1) { | ||||
|   return window['go']['main']['App']['SaveLending'](arg1); | ||||
| } | ||||
|   | ||||
| @@ -1,35 +1,174 @@ | ||||
| export namespace model { | ||||
| 	 | ||||
| 	export class SubThing { | ||||
| 	export class Client { | ||||
| 	    ID: number; | ||||
| 	    ThingID: number; | ||||
| 	    // Go type: time | ||||
| 	    CreatedAt: any; | ||||
| 	    // Go type: time | ||||
| 	    UpdatedAt: any; | ||||
| 	    // Go type: gorm | ||||
| 	    DeletedAt: any; | ||||
| 	    Name: string; | ||||
| 	    Email: string; | ||||
| 	    Lendings: Lending[]; | ||||
| 	 | ||||
| 	    static createFrom(source: any = {}) { | ||||
| 	        return new SubThing(source); | ||||
| 	        return new Client(source); | ||||
| 	    } | ||||
| 	 | ||||
| 	    constructor(source: any = {}) { | ||||
| 	        if ('string' === typeof source) source = JSON.parse(source); | ||||
| 	        this.ID = source["ID"]; | ||||
| 	        this.ThingID = source["ThingID"]; | ||||
| 	        this.CreatedAt = this.convertValues(source["CreatedAt"], null); | ||||
| 	        this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); | ||||
| 	        this.DeletedAt = this.convertValues(source["DeletedAt"], null); | ||||
| 	        this.Name = source["Name"]; | ||||
| 	        this.Email = source["Email"]; | ||||
| 	        this.Lendings = this.convertValues(source["Lendings"], Lending); | ||||
| 	    } | ||||
| 	 | ||||
| 		convertValues(a: any, classs: any, asMap: boolean = false): any { | ||||
| 		    if (!a) { | ||||
| 		        return a; | ||||
| 		    } | ||||
| 		    if (a.slice && a.map) { | ||||
| 		        return (a as any[]).map(elem => this.convertValues(elem, classs)); | ||||
| 		    } else if ("object" === typeof a) { | ||||
| 		        if (asMap) { | ||||
| 		            for (const key of Object.keys(a)) { | ||||
| 		                a[key] = new classs(a[key]); | ||||
| 		            } | ||||
| 		            return a; | ||||
| 		        } | ||||
| 		        return new classs(a); | ||||
| 		    } | ||||
| 		    return a; | ||||
| 		} | ||||
| 	} | ||||
| 	export class Thing { | ||||
| 	export class Lending { | ||||
| 	    ID: number; | ||||
| 	    Name: string; | ||||
| 	    Subthings: SubThing[]; | ||||
| 	    // Go type: time | ||||
| 	    CreatedAt: any; | ||||
| 	    // Go type: time | ||||
| 	    UpdatedAt: any; | ||||
| 	    // Go type: gorm | ||||
| 	    DeletedAt: any; | ||||
| 	    // Go type: time | ||||
| 	    DueDate: any; | ||||
| 	    Returned: boolean; | ||||
| 	    ClientID: number; | ||||
| 	    Client: Client; | ||||
| 	    BookID: number; | ||||
| 	    Book: Book; | ||||
| 	 | ||||
| 	    static createFrom(source: any = {}) { | ||||
| 	        return new Thing(source); | ||||
| 	        return new Lending(source); | ||||
| 	    } | ||||
| 	 | ||||
| 	    constructor(source: any = {}) { | ||||
| 	        if ('string' === typeof source) source = JSON.parse(source); | ||||
| 	        this.ID = source["ID"]; | ||||
| 	        this.CreatedAt = this.convertValues(source["CreatedAt"], null); | ||||
| 	        this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); | ||||
| 	        this.DeletedAt = this.convertValues(source["DeletedAt"], null); | ||||
| 	        this.DueDate = this.convertValues(source["DueDate"], null); | ||||
| 	        this.Returned = source["Returned"]; | ||||
| 	        this.ClientID = source["ClientID"]; | ||||
| 	        this.Client = this.convertValues(source["Client"], Client); | ||||
| 	        this.BookID = source["BookID"]; | ||||
| 	        this.Book = this.convertValues(source["Book"], Book); | ||||
| 	    } | ||||
| 	 | ||||
| 		convertValues(a: any, classs: any, asMap: boolean = false): any { | ||||
| 		    if (!a) { | ||||
| 		        return a; | ||||
| 		    } | ||||
| 		    if (a.slice && a.map) { | ||||
| 		        return (a as any[]).map(elem => this.convertValues(elem, classs)); | ||||
| 		    } else if ("object" === typeof a) { | ||||
| 		        if (asMap) { | ||||
| 		            for (const key of Object.keys(a)) { | ||||
| 		                a[key] = new classs(a[key]); | ||||
| 		            } | ||||
| 		            return a; | ||||
| 		        } | ||||
| 		        return new classs(a); | ||||
| 		    } | ||||
| 		    return a; | ||||
| 		} | ||||
| 	} | ||||
| 	export class Book { | ||||
| 	    ID: number; | ||||
| 	    // Go type: time | ||||
| 	    CreatedAt: any; | ||||
| 	    // Go type: time | ||||
| 	    UpdatedAt: any; | ||||
| 	    // Go type: gorm | ||||
| 	    DeletedAt: any; | ||||
| 	    Title: string; | ||||
| 	    ISBN: string; | ||||
| 	    AuthorID: number; | ||||
| 	    Author: Author; | ||||
| 	    Lendings: Lending[]; | ||||
| 	 | ||||
| 	    static createFrom(source: any = {}) { | ||||
| 	        return new Book(source); | ||||
| 	    } | ||||
| 	 | ||||
| 	    constructor(source: any = {}) { | ||||
| 	        if ('string' === typeof source) source = JSON.parse(source); | ||||
| 	        this.ID = source["ID"]; | ||||
| 	        this.CreatedAt = this.convertValues(source["CreatedAt"], null); | ||||
| 	        this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); | ||||
| 	        this.DeletedAt = this.convertValues(source["DeletedAt"], null); | ||||
| 	        this.Title = source["Title"]; | ||||
| 	        this.ISBN = source["ISBN"]; | ||||
| 	        this.AuthorID = source["AuthorID"]; | ||||
| 	        this.Author = this.convertValues(source["Author"], Author); | ||||
| 	        this.Lendings = this.convertValues(source["Lendings"], Lending); | ||||
| 	    } | ||||
| 	 | ||||
| 		convertValues(a: any, classs: any, asMap: boolean = false): any { | ||||
| 		    if (!a) { | ||||
| 		        return a; | ||||
| 		    } | ||||
| 		    if (a.slice && a.map) { | ||||
| 		        return (a as any[]).map(elem => this.convertValues(elem, classs)); | ||||
| 		    } else if ("object" === typeof a) { | ||||
| 		        if (asMap) { | ||||
| 		            for (const key of Object.keys(a)) { | ||||
| 		                a[key] = new classs(a[key]); | ||||
| 		            } | ||||
| 		            return a; | ||||
| 		        } | ||||
| 		        return new classs(a); | ||||
| 		    } | ||||
| 		    return a; | ||||
| 		} | ||||
| 	} | ||||
| 	export class Author { | ||||
| 	    ID: number; | ||||
| 	    // Go type: time | ||||
| 	    CreatedAt: any; | ||||
| 	    // Go type: time | ||||
| 	    UpdatedAt: any; | ||||
| 	    // Go type: gorm | ||||
| 	    DeletedAt: any; | ||||
| 	    Name: string; | ||||
| 	    Books: Book[]; | ||||
| 	 | ||||
| 	    static createFrom(source: any = {}) { | ||||
| 	        return new Author(source); | ||||
| 	    } | ||||
| 	 | ||||
| 	    constructor(source: any = {}) { | ||||
| 	        if ('string' === typeof source) source = JSON.parse(source); | ||||
| 	        this.ID = source["ID"]; | ||||
| 	        this.CreatedAt = this.convertValues(source["CreatedAt"], null); | ||||
| 	        this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); | ||||
| 	        this.DeletedAt = this.convertValues(source["DeletedAt"], null); | ||||
| 	        this.Name = source["Name"]; | ||||
| 	        this.Subthings = this.convertValues(source["Subthings"], SubThing); | ||||
| 	        this.Books = this.convertValues(source["Books"], Book); | ||||
| 	    } | ||||
| 	 | ||||
| 		convertValues(a: any, classs: any, asMap: boolean = false): any { | ||||
| @@ -51,5 +190,7 @@ export namespace model { | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										15
									
								
								frontend/wailsjs/go/things/Service.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								frontend/wailsjs/go/things/Service.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,15 +0,0 @@ | ||||
| // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL | ||||
| // This file is automatically generated. DO NOT EDIT | ||||
| import {model} from '../models'; | ||||
|  | ||||
| export function AddSubThing(arg1:number,arg2:string):Promise<void>; | ||||
|  | ||||
| export function DeleteSubThing(arg1:number):Promise<void>; | ||||
|  | ||||
| export function DeleteThing(arg1:number):Promise<void>; | ||||
|  | ||||
| export function GetSubThings(arg1:number):Promise<Array<model.SubThing>>; | ||||
|  | ||||
| export function GetThings():Promise<Array<model.Thing>>; | ||||
|  | ||||
| export function NewThing(arg1:string):Promise<void>; | ||||
| @@ -1,27 +0,0 @@ | ||||
| // @ts-check | ||||
| // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL | ||||
| // This file is automatically generated. DO NOT EDIT | ||||
|  | ||||
| export function AddSubThing(arg1, arg2) { | ||||
|   return window['go']['things']['Service']['AddSubThing'](arg1, arg2); | ||||
| } | ||||
|  | ||||
| export function DeleteSubThing(arg1) { | ||||
|   return window['go']['things']['Service']['DeleteSubThing'](arg1); | ||||
| } | ||||
|  | ||||
| export function DeleteThing(arg1) { | ||||
|   return window['go']['things']['Service']['DeleteThing'](arg1); | ||||
| } | ||||
|  | ||||
| export function GetSubThings(arg1) { | ||||
|   return window['go']['things']['Service']['GetSubThings'](arg1); | ||||
| } | ||||
|  | ||||
| export function GetThings() { | ||||
|   return window['go']['things']['Service']['GetThings'](); | ||||
| } | ||||
|  | ||||
| export function NewThing(arg1) { | ||||
|   return window['go']['things']['Service']['NewThing'](arg1); | ||||
| } | ||||
							
								
								
									
										9
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								main.go
									
									
									
									
									
								
							| @@ -2,8 +2,7 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"embed" | ||||
| 	"wails-template/model" | ||||
| 	"wails-template/things" | ||||
| 	"library-manager/model" | ||||
|  | ||||
| 	"github.com/wailsapp/wails/v2" | ||||
| 	"github.com/wailsapp/wails/v2/pkg/options" | ||||
| @@ -15,13 +14,12 @@ var assets embed.FS | ||||
|  | ||||
| func main() { | ||||
| 	// Create an instance of the app structure | ||||
| 	app := NewApp() | ||||
| 	db := model.InitDB() | ||||
| 	things := &things.Service{DB: db} | ||||
| 	app := NewApp(db) | ||||
|  | ||||
| 	// Create application with options | ||||
| 	err := wails.Run(&options.App{ | ||||
| 		Title:  "wails-template", | ||||
| 		Title:  "library-manager", | ||||
| 		Width:  1024, | ||||
| 		Height: 768, | ||||
| 		AssetServer: &assetserver.Options{ | ||||
| @@ -31,7 +29,6 @@ func main() { | ||||
| 		OnStartup:        app.startup, | ||||
| 		Bind: []interface{}{ | ||||
| 			app, | ||||
| 			things, | ||||
| 		}, | ||||
| 	}) | ||||
|  | ||||
|   | ||||
| @@ -4,21 +4,44 @@ import ( | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"time" | ||||
|  | ||||
| 	"gorm.io/driver/sqlite" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| type Thing struct { | ||||
| 	ID        int | ||||
| type Author struct { | ||||
| 	gorm.Model | ||||
| 	Name  string | ||||
| 	Subthings []SubThing | ||||
| 	Books []Book | ||||
| } | ||||
|  | ||||
| type SubThing struct { | ||||
| 	ID      int | ||||
| 	ThingID int | ||||
| type Book struct { | ||||
| 	gorm.Model | ||||
| 	Title    string | ||||
| 	ISBN     string | ||||
| 	AuthorID uint | ||||
| 	Author   Author    `gorm:"foreignKey:AuthorID"` | ||||
| 	Lendings []Lending `gorm:"foreignKey:BookID"` | ||||
| } | ||||
|  | ||||
| type Client struct { | ||||
| 	gorm.Model | ||||
| 	Name     string | ||||
| 	Email    string | ||||
| 	Lendings []Lending `gorm:"foreignKey:ClientID"` | ||||
| } | ||||
|  | ||||
| type Lending struct { | ||||
| 	gorm.Model | ||||
| 	DueDate  time.Time | ||||
| 	Returned bool | ||||
|  | ||||
| 	ClientID uint | ||||
| 	Client   Client `gorm:"foreignKey:ClientID"` | ||||
|  | ||||
| 	BookID uint | ||||
| 	Book   Book `gorm:"foreignKey:BookID"` | ||||
| } | ||||
|  | ||||
| func InitDB() *gorm.DB { | ||||
| @@ -26,10 +49,10 @@ func InitDB() *gorm.DB { | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	db, err := gorm.Open(sqlite.Open(path.Join(home, "things.db"))) | ||||
| 	db, err := gorm.Open(sqlite.Open(path.Join(home, "library.db"))) | ||||
| 	if err != nil { | ||||
| 		log.Panic(err) | ||||
| 	} | ||||
| 	db.AutoMigrate(&Thing{}, &SubThing{}) | ||||
| 	db.AutoMigrate(&Author{}, &Book{}, &Client{}, &Lending{}) | ||||
| 	return db | ||||
| } | ||||
|   | ||||
| @@ -1,54 +0,0 @@ | ||||
| package things | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"wails-template/model" | ||||
|  | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| type Service struct { | ||||
| 	DB *gorm.DB | ||||
| } | ||||
|  | ||||
| func (s *Service) NewThing(name string) { | ||||
| 	if err := s.DB.Save(&model.Thing{Name: name}).Error; err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	print(name) | ||||
| } | ||||
|  | ||||
| func (s *Service) GetThings() (things []model.Thing) { | ||||
| 	if err := s.DB.Find(&things).Error; err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	return things | ||||
| } | ||||
|  | ||||
| func (s *Service) DeleteThing(id int) { | ||||
| 	if err := s.DB.Delete(model.Thing{}, id).Error; err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *Service) GetSubThings(thingID int) (subthings []model.SubThing) { | ||||
| 	if err := s.DB.Where("thing_id = ?", thingID).Find(&subthings).Error; err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| func (s *Service) AddSubThing(thingID int, name string) { | ||||
| 	if err := s.DB.Save(&model.SubThing{ | ||||
| 		ThingID: thingID, | ||||
| 		Name:    name, | ||||
| 	}).Error; err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *Service) DeleteSubThing(id int) { | ||||
| 	if err := s.DB.Delete(model.SubThing{}, id).Error; err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										8
									
								
								utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| package main | ||||
|  | ||||
| func Greater(a, b uint) uint { | ||||
| 	if a > b { | ||||
| 		return a | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "$schema": "https://wails.io/schemas/config.v2.json", | ||||
|   "name": "wails-template", | ||||
|   "outputfilename": "wails-template", | ||||
|   "name": "library-manager", | ||||
|   "outputfilename": "library-manager", | ||||
|   "frontend:install": "pnpm install", | ||||
|   "frontend:build": "pnpm run build", | ||||
|   "frontend:dev:watcher": "pnpm run dev", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user