Update. Add svelte admin frontend

This commit is contained in:
2025-06-12 20:38:12 +02:00
parent 17ab7c4929
commit 562186fa47
16 changed files with 463 additions and 165 deletions

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,45 +1,139 @@
<script lang="ts">
import svelteLogo from "./assets/svelte.svg";
import Counter from "./lib/Counter.svelte";
import Table from "./lib/Table.svelte";
import GenericForm from "./lib/GenericForm.svelte";
type Retailer = {
id: number;
shopname: string;
url: string;
country: string;
display: number;
};
let records: Retailer[] = $state([]);
let columns: string[] = ["shopname", "country", "url"];
let activeTab = $state("invoice");
async function fetchData(endpoint: string) {
const res = await fetch("http://localhost:8080/api/" + endpoint);
records = await res.json();
}
$effect(() => {
if (activeTab === "retailers") {
fetchData("retailers/");
}
});
let showModal = $state(false);
function openModal() {
showModal = true;
}
function closeModal() {
showModal = false;
}
let formdata: Retailer = {
shopname: "",
url: "",
id: 0,
country: "",
display: 0,
};
function submitUserForm(data: Retailer) {
closeModal();
console.log("User Form Submitted:", data);
}
function removeRowHandler(id: Number) {
console.log("Remove Record:", id);
}
</script>
<!-- Bootstrap Modal -->
<div
class="modal fade {showModal ? 'show' : ''}"
tabindex="-1"
role="dialog"
style="display: {showModal ? 'block' : 'none'};"
>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Retailer</h5>
</div>
<div class="modal-body">
<GenericForm formData={formdata} onSubmit={submitUserForm}>
{#each columns as col}
<div class="mb-3">
<label for={col}>{col}:</label>
<input
id={col}
type="text"
bind:value={formdata[col as keyof Retailer]}
/>
</div>
{/each}
</GenericForm>
</div>
<div class="modal-footer justify-content-end">
<button type="button" class="btn btn-secondary" onclick={closeModal}
>Close</button
>
</div>
</div>
</div>
</div>
<main>
<div>
<a href="https://svelte.dev" target="_blank" rel="noreferrer">
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
</a>
<ul class="nav nav-tabs">
<li class="nav-item">
<a
class="nav-link {activeTab === 'invoice' ? 'active' : ''}"
href="#"
onclick={() => (activeTab = "invoice")}>Invoices</a
>
</li>
<li class="nav-item">
<a
class="nav-link {activeTab === 'retailers' ? 'active' : ''}"
href="#"
onclick={() => (activeTab = "retailers")}>Retailers</a
>
</li>
<li class="nav-item">
<a
class="nav-link {activeTab === 'news' ? 'active' : ''}"
href="#"
onclick={() => (activeTab = "news")}>News</a
>
</li>
</ul>
<div class="container-fluid">
{#if activeTab === "invoice"}
<p>Invoice list</p>
{/if}
{#if activeTab === "retailers"}
<div class="row justify-content-start">
<div class="col-2 mt-3">
<button
onclick={openModal}
type="button"
class="btn btn-outline-primary">Add retailer</button
>
</div>
</div>
<div class="row">
<div class="col">
<Table {columns} {records} {removeRowHandler} />
</div>
</div>
{/if}
{#if activeTab === "news"}
<p>news</p>
{/if}
</div>
<h1>Vite + Svelte</h1>
<div class="card">
<Counter />
</div>
<p>
Check out <a
href="https://github.com/sveltejs/kit#readme"
target="_blank"
rel="noreferrer">SvelteKit</a
>, the official Svelte app framework powered by Vite!
</p>
<p class="read-the-docs">Click on the Vite and Svelte logos to learn more</p>
</main>
<style>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.svelte:hover {
filter: drop-shadow(0 0 2em #ff3e00aa);
}
.read-the-docs {
color: #888;
}
</style>

View File

@@ -0,0 +1,14 @@
<script lang="ts" generics="T">
export let formData: T;
export let onSubmit: (data: T) => void;
function handleSubmit(event: SubmitEvent) {
event.preventDefault();
onSubmit(formData);
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<slot />
<button type="submit">Submit</button>
</form>

View File

@@ -0,0 +1,48 @@
<script lang="ts">
interface Props {
columns: string[];
records: any;
removeRowHandler: (id: number) => void;
}
let { columns, records, removeRowHandler }: Props = $props();
</script>
<table class="table">
<thead>
<tr>
{#each columns as col}
<th scope="col">{col}</th>
{/each}
<th scope="col">edit</th>
<th scope="col">remove</th>
</tr>
</thead>
<tbody>
{#each records as row}
<tr>
{#each columns as col}
<td>{row[col]}</td>
{/each}
<td>
<button
onclick={() => removeRowHandler(row.id)}
class="btn btn-outline-success align-items-center"
aria-label="Edit record"
>
<i class="bi bi-pencil"></i>
</button>
</td>
<td>
<button
onclick={() => removeRowHandler(row.id)}
class="btn btn-outline-danger align-items-center"
aria-label="Remove record"
>
<i class="bi bi-x-lg"></i>
</button>
</td>
</tr>
{/each}
</tbody>
</table>

View File

@@ -4,4 +4,7 @@ import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vite.dev/config/
export default defineConfig({
plugins: [svelte()],
build: {
manifest: true
}
})

View File

@@ -2,6 +2,8 @@ package web
import (
"embed"
"fmt"
"io/fs"
"net/http"
"text/template"
@@ -19,9 +21,17 @@ type PageData struct {
//go:embed templates
var Templates embed.FS
//go:embed frontend/dist
//go:embed all:frontend/dist
var StaticAssets embed.FS
func DistFS() fs.FS {
efs, err := fs.Sub(StaticAssets, "frontend/dist")
if err != nil {
panic(fmt.Sprintf("unable to serve frontend: %v", err))
}
return efs
}
func RenderTemplate(w http.ResponseWriter, name string, data PageData) error {
tpl := template.Must(template.ParseFS(Templates, "templates/"+name+".html", "templates/base.html"))
return tpl.ExecuteTemplate(w, "base", data)

View File

@@ -1,3 +1,3 @@
{{define "vite"}} {{ .Vite.Tags }} {{end}} {{define "content"}}
<div id="root"></div>
<div id="app"></div>
{{end}}