Add basic frontend structure
This commit is contained in:
parent
486977f828
commit
25b7c24c65
29 changed files with 4659 additions and 3811 deletions
97
frontend/src/routes/(navbar)/+layout.svelte
Normal file
97
frontend/src/routes/(navbar)/+layout.svelte
Normal file
|
@ -0,0 +1,97 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { persistentSettings } from '$lib/persistent-store'
|
||||
import i18n from '$lib/i18n'
|
||||
let { children } = $props()
|
||||
|
||||
if (!$persistentSettings.loggedIn) {
|
||||
goto('/login')
|
||||
}
|
||||
|
||||
let drawerOpen = $state(false)
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full p-4 flex flex-col bg-base-200">
|
||||
<div class="navbar w-auto mb-4 pr-4 bg-base-100 shadow-md rounded-md">
|
||||
<div class="drawer navbar-start">
|
||||
<input bind:checked={drawerOpen} id="nav-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content">
|
||||
<label for="nav-drawer" class="btn btn-ghost btn-circle drawer-button">
|
||||
<i class="bi bi-list text-2xl"></i>
|
||||
</label>
|
||||
</div>
|
||||
<div class="drawer-side z-10">
|
||||
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-100 text-base min-h-full w-80 p-4">
|
||||
<li class="mb-4">
|
||||
<a class="btn btn-ghost text-xl" href="/">LibreCharge</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
onclick={() => {
|
||||
drawerOpen = !drawerOpen
|
||||
}}
|
||||
href="/"
|
||||
>
|
||||
<i class="bi bi-graph-up text-xl"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
onclick={() => {
|
||||
drawerOpen = !drawerOpen
|
||||
}}
|
||||
href="/idtoken"
|
||||
>
|
||||
<i class="bi bi-credit-card-fill text-xl"></i>
|
||||
<span>{$i18n.t('common:navbar.link.idtoken')}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
onclick={() => {
|
||||
drawerOpen = !drawerOpen
|
||||
}}
|
||||
href="/transaction"
|
||||
>
|
||||
<i class="bi bi-battery-charging text-xl"></i>
|
||||
<span>{$i18n.t('common:navbar.link.transaction')}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
onclick={() => {
|
||||
drawerOpen = !drawerOpen
|
||||
}}
|
||||
href="/chargepoint"
|
||||
>
|
||||
<i class="bi bi-plug-fill text-xl"></i>
|
||||
<span>{$i18n.t('common:navbar.link.chargepoint')}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-center">
|
||||
<a class="btn btn-ghost text-xl" href="/">LibreCharge</a>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
|
||||
<i class="bi bi-person-circle text-2xl"></i>
|
||||
</div>
|
||||
<ul
|
||||
tabindex="-1"
|
||||
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"
|
||||
>
|
||||
<li><a href="/profile">{$i18n.t('common:navbar.link.profile')}</a></li>
|
||||
<li><a href="/login?logout">{$i18n.t('common:navbar.link.logout')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-full flex flex-col">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
121
frontend/src/routes/(navbar)/+page.svelte
Normal file
121
frontend/src/routes/(navbar)/+page.svelte
Normal file
|
@ -0,0 +1,121 @@
|
|||
<script lang="ts">
|
||||
import { persistentSettings } from '$lib/persistent-store'
|
||||
import { currentDaytime } from '$lib/util'
|
||||
import i18n from '$lib/i18n'
|
||||
import DashboardCard from '$lib/component/DashboardCard.svelte'
|
||||
import TransactionTable from '$lib/component/TransactionTable.svelte'
|
||||
|
||||
$i18n.loadNamespaces('dashboard')
|
||||
|
||||
let hasActiveTransaction = $state(true)
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full mt-10 flex flex-col">
|
||||
<p class="w-full text-2xl font-bold">
|
||||
{$i18n.t('dashboard:greeting.' + currentDaytime(), { name: $persistentSettings.friendlyName })}
|
||||
</p>
|
||||
<div class="w-full mt-5 flex gap-x-4">
|
||||
<div class="card bg-base-100 shadow rounded-md basis-0 grow">
|
||||
<div class="card-body gap-2">
|
||||
<div class="flex items-start justify-between gap-2 text-sm">
|
||||
<div>
|
||||
<p class="text-base-content/80 font-medium">
|
||||
{$i18n.t('dashboard:cards.currentTransaction')}
|
||||
</p>
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
{#if hasActiveTransaction}
|
||||
<div class="inline-grid *:[grid-area:1/1]">
|
||||
<div class="status status-success animate-ping"></div>
|
||||
<div class="status status-success"></div>
|
||||
</div>
|
||||
{/if}
|
||||
<p class="text-2xl font-semibold">
|
||||
{#if hasActiveTransaction}0,25 kWh{:else}-{/if}
|
||||
</p>
|
||||
{#if hasActiveTransaction}
|
||||
<div class="badge badge-soft badge-success badge-sm gap-0.5 px-1 font-medium">
|
||||
2,30 €
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<i class="bi bi-plug-fill text-primary text-4xl"></i>
|
||||
</div>
|
||||
<div class="w-full flex flex-row items-center">
|
||||
<p class="text-base-content/60 text-sm">
|
||||
{#if hasActiveTransaction}
|
||||
{$i18n.t('dashboard:cards.chargepoint', { name: 'DE-EXMPL-0001' })}
|
||||
{:else}
|
||||
{$i18n.t('dashboard:cards.noCurrentTransaction')}
|
||||
{/if}
|
||||
</p>
|
||||
{#if hasActiveTransaction}
|
||||
<button class="btn btn-xs btn-primary">
|
||||
{$i18n.t('dashboard:cards.toCurrentTransactionButton')}
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardCard
|
||||
title={$i18n.t('dashboard:cards.transactionCount')}
|
||||
icon={'bi-battery-charging'}
|
||||
value={3}
|
||||
previousValue={1}
|
||||
/>
|
||||
<DashboardCard
|
||||
title={$i18n.t('dashboard:cards.transactionEnergyTotal')}
|
||||
icon={'bi-lightning-charge-fill'}
|
||||
value={180}
|
||||
previousValue={50}
|
||||
unit={'kWh'}
|
||||
/>
|
||||
<DashboardCard
|
||||
title={$i18n.t('dashboard:cards.transactionCostTotal')}
|
||||
icon={'bi-currency-exchange'}
|
||||
value={30.56}
|
||||
previousValue={30.56}
|
||||
unit={'€'}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full flex flex-col mt-10">
|
||||
<p class="text-xl font-bold">{$i18n.t('dashboard:table.title')}</p>
|
||||
<TransactionTable
|
||||
transactions={[
|
||||
{
|
||||
id: '1',
|
||||
begin: new Date(Date.UTC(2025, 3, 15, 14, 12, 23)),
|
||||
end: new Date(Date.UTC(2025, 3, 15, 16, 18, 46)),
|
||||
chargepoint: { name: 'DE-EXMPL-0001', id: '1' },
|
||||
energyAmmount: 58.65,
|
||||
cost: 36.45,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
begin: new Date(Date.UTC(2025, 3, 15, 14, 12, 23)),
|
||||
end: new Date(Date.UTC(2025, 3, 15, 16, 18, 46)),
|
||||
chargepoint: { name: 'DE-EXMPL-0001', id: '1' },
|
||||
energyAmmount: 58.65,
|
||||
cost: 36.45,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
begin: new Date(Date.UTC(2025, 3, 15, 14, 12, 23)),
|
||||
end: new Date(Date.UTC(2025, 3, 15, 16, 18, 46)),
|
||||
chargepoint: { name: 'DE-EXMPL-0001', id: '1' },
|
||||
energyAmmount: 58.65,
|
||||
cost: 36.45,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
begin: new Date(Date.UTC(2025, 3, 15, 14, 12, 23)),
|
||||
end: new Date(Date.UTC(2025, 3, 15, 16, 18, 46)),
|
||||
chargepoint: { name: 'DE-EXMPL-0001', id: '1' },
|
||||
energyAmmount: 58.65,
|
||||
cost: 36.45,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
0
frontend/src/routes/(navbar)/chargepoint/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/chargepoint/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/idtoken/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/idtoken/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/profile/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/profile/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/transaction/+page.svelte
Normal file
0
frontend/src/routes/(navbar)/transaction/+page.svelte
Normal file
38
frontend/src/routes/+layout.svelte
Normal file
38
frontend/src/routes/+layout.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script>
|
||||
import { persistentSettings } from '$lib/persistent-store'
|
||||
let { children } = $props()
|
||||
import '../app.css'
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="w-screen h-screen flex flex-col"
|
||||
data-theme={$persistentSettings.darkmode ? 'darkgray' : 'emerald'}
|
||||
>
|
||||
{@render children()}
|
||||
<footer
|
||||
class="footer sm:footer-horizontal bg-neutral text-neutral-content items-center pt-3 pb-3 pl-6 pr-6"
|
||||
>
|
||||
<aside class="grid-flow-col">
|
||||
<p>Powered by LibreCharge - Licensed under GNU AGPL v3</p>
|
||||
</aside>
|
||||
<nav class="grid-flow-col gap-4 md:place-self-center md:justify-self-end text-3xl">
|
||||
<label class="swap swap-rotate">
|
||||
<!-- this hidden checkbox controls the state -->
|
||||
<input type="checkbox" bind:checked={$persistentSettings.darkmode} />
|
||||
|
||||
<!-- sun icon -->
|
||||
<i class="bi bi-sun swap-off"></i>
|
||||
|
||||
<!-- moon icon -->
|
||||
<i class="bi bi-moon swap-on"></i>
|
||||
</label>
|
||||
<a
|
||||
aria-label="Source Code"
|
||||
href="https://git.bluemedia.dev/Bluemedia/simple-ocpp-cs"
|
||||
target="_blank"
|
||||
>
|
||||
<i class="bi bi-git"></i>
|
||||
</a>
|
||||
</nav>
|
||||
</footer>
|
||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||
<script>
|
||||
import '../style.css'
|
||||
</script>
|
||||
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
57
frontend/src/routes/login/+page.svelte
Normal file
57
frontend/src/routes/login/+page.svelte
Normal file
|
@ -0,0 +1,57 @@
|
|||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition'
|
||||
import i18n from '$lib/i18n'
|
||||
$i18n.loadNamespaces('login')
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const isReauth = urlParams.has('reauth')
|
||||
const isLogout = urlParams.has('logout')
|
||||
|
||||
let showToast = $state(true)
|
||||
if (isReauth || isLogout) {
|
||||
setTimeout(() => {
|
||||
showToast = false
|
||||
}, 6000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full flex flex-col justify-center items-center bg-base-200">
|
||||
<div class="basis-0 grow flex justify-center items-center">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<i class="bi bi-ev-front-fill text-8xl text-primary"></i>
|
||||
<p class="text-2xl font-bold">LibreCharge</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="basis-0 flex justify-center items-center">
|
||||
<div class="card bg-base-100 w-full max-w-sm h-fit shrink-0 shadow-2xl">
|
||||
<div class="card-body">
|
||||
<h1 class="text-2xl font-bold">{$i18n.t('login:title')}</h1>
|
||||
<p class="py-2 text-base">{$i18n.t('login:text')}</p>
|
||||
<fieldset class="fieldset">
|
||||
<label class="fieldset-label input text-base-content">
|
||||
<i class="bi bi-envelope-at"></i>
|
||||
<input type="email" placeholder="me@example.com" required />
|
||||
</label>
|
||||
<label class="fieldset-label input text-base-content">
|
||||
<i class="bi bi-key"></i>
|
||||
<input type="password" placeholder={$i18n.t('login:passwordPlaceholder')} required />
|
||||
</label>
|
||||
<button class="btn btn-primary mt-4">{$i18n.t('login:button')}</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="basis-0 grow"></span>
|
||||
{#if (isReauth || isLogout) && showToast}
|
||||
<div class="toast toast-top" out:fly={{ x: 200 }}>
|
||||
<div class="alert text-base {isReauth ? 'alert-warning' : 'alert-success'}">
|
||||
{#if isReauth}
|
||||
<i class="bi bi-exclamation-diamond text-xl"></i>
|
||||
{:else}
|
||||
<i class="bi bi-check-circle text-xl"></i>
|
||||
{/if}
|
||||
<span>{$i18n.t(isReauth ? 'login:toast.reauth' : 'login:toast.logoutSuccess')}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue