aboutsummaryrefslogtreecommitdiff
path: root/fe/src/lib
diff options
context:
space:
mode:
authorDoog <157747121+doogongithub@users.noreply.github.com>2024-02-24 20:08:35 -0500
committerDoog <157747121+doogongithub@users.noreply.github.com>2024-02-24 20:08:35 -0500
commite37c73e33a4aaf7fb8d25b5af03627f20bcda19f (patch)
tree277a534e826325e25f881e61e322b4e2e7ec94f9 /fe/src/lib
parent3eafb413a48cde60dea8a7355ee621c6acca952f (diff)
add gitignore
Diffstat (limited to 'fe/src/lib')
-rw-r--r--fe/src/lib/DataView.svelte67
-rw-r--r--fe/src/lib/Layout.svelte57
-rw-r--r--fe/src/lib/LoginForm.svelte64
-rw-r--r--fe/src/lib/Table.svelte61
4 files changed, 241 insertions, 8 deletions
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte
new file mode 100644
index 0000000..cd7b042
--- /dev/null
+++ b/fe/src/lib/DataView.svelte
@@ -0,0 +1,67 @@
1<script lang='ts'>
2import { onMount } from 'svelte';
3import { token } from '../stores/auth'
4import Table from './Table.svelte';
5
6let json;
7let showAddForm: boolean = false;
8
9async function fetchData() {
10 const res = await fetch('http://localhost:8080/api/v1/stats/', {
11 method: "GET",
12 headers: {
13 'Authorization': `Bearer ${$token}`
14 }
15 });
16 if (res.ok) {
17 json = res.json();
18 } else {
19 throw new Error('There was a problem with your request');
20 }
21}
22
23async function submitStat() {
24 const response = await fetch('http://localhost:8080/api/v1/stats/', {
25 method: "POST",
26 headers: {
27 'Authorization': `Bearer ${$token}`
28 },
29 body: JSON.stringify({
30 date: new Date,
31 user_id: 1,
32 quantity: 3
33 })
34 });
35 fetchData();
36}
37
38function handleClick() {
39 showAddForm = true;
40}
41
42function handleAddDialogSubmit (e) {
43 console.log(e.keyCode)
44}
45
46onMount(() => {
47 fetchData();
48});
49
50</script>
51<div>
52 <button on:click={submitStat}>Add Stat Test</button>
53 <dialog open={showAddForm} on:submit={handleAddDialogSubmit}>
54 <form method="dialog">
55 <input name="date" type="date" />
56 <input name="quantity" type="number" min="0" autocomplete="off"/>
57 <button type="submit">Submit</button>
58 </form>
59 </dialog>
60 <button on:click={handleClick}>Add</button>
61 {#await json then data}
62 <Table {data} nofooter />
63 {:catch error}
64 <p>{error}</p>
65 {/await}
66 <!-- <Chart /> -->
67</div>
diff --git a/fe/src/lib/Layout.svelte b/fe/src/lib/Layout.svelte
new file mode 100644
index 0000000..f349632
--- /dev/null
+++ b/fe/src/lib/Layout.svelte
@@ -0,0 +1,57 @@
1<script>
2import { authenticated, token } from '../stores/auth';
3
4const logout = () => token.unauthenticate();
5
6function showSettingsDialog() {
7 console.log('show settings');
8}
9
10</script>
11
12<div class="layout">
13 {#if $authenticated}
14 <nav>
15 <div>
16 <h1>Water</h1>
17 </div>
18 <div>
19 <button on:click={showSettingsDialog}>Settings</button>
20 <button on:click={logout}>Logout</button>
21 </div>
22 </nav>
23 {/if}
24 <div id="content">
25 <slot />
26 </div>
27</div>
28
29<style>
30.layout {
31 height: 100vh;
32}
33nav {
34 display: flex;
35 flex-direction: row;
36 align-items: center;
37 justify-content: space-between;
38 height: 64px;
39 padding: 0 2em;
40}
41
42nav div {
43 width: fit-content;
44}
45
46nav div h1 {
47 font-size: 1.75em;
48}
49
50#content {
51 display: flex;
52 flex-direction: column;
53 justify-content: center;
54 align-items: center;
55 padding: 3em 0;
56}
57</style>
diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte
new file mode 100644
index 0000000..22c0faf
--- /dev/null
+++ b/fe/src/lib/LoginForm.svelte
@@ -0,0 +1,64 @@
1<script lang='ts'>
2import { token } from '../stores/auth';
3import Card from './Card.svelte';
4
5let user = {
6 username: '',
7 password: ''
8}
9
10let error;
11
12interface CredentialObject {
13 username: string;
14 password: string;
15}
16
17function prepareCredentials ({ username, password }: CredentialObject): string {
18 return btoa(`${username}:${password}`);
19}
20
21async function onSubmit (e) {
22 if (!user.username || !user.password) {
23 error = 'please enter your username and password';
24 return;
25 }
26 const auth = prepareCredentials(user);
27
28 const response = await fetch('http://localhost:8080/api/v1/auth', {
29 method: 'POST',
30 headers: {
31 'Authorization': `Basic ${auth}`,
32 },
33 });
34
35 if (response.status === 401) {
36 error = "Your username or password is wrong";
37 return;
38 }
39
40 if (response.ok) {
41 const { token: apiToken } = await response.json();
42 token.authenticate(apiToken);
43 }
44
45 error = null;
46}
47</script>
48
49<Card>
50 <form class="form" on:submit|preventDefault={onSubmit}>
51 <div class='form input group'>
52 <label for="username">Username</label>
53 <input bind:value={user.username} id="username" name='username' type="text" autocomplete="username" />
54 </div>
55 <div class='form input group'>
56 <label for="password">Password</label>
57 <input bind:value={user.password} id="password" name='password' type="password" autocomplete="current-password"/>
58 </div>
59 {#if error}
60 <p class="error">{error}</p>
61 {/if}
62 <button type="submit">Log in</button>
63 </form>
64</Card>
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte
index 2df9f8c..5572280 100644
--- a/fe/src/lib/Table.svelte
+++ b/fe/src/lib/Table.svelte
@@ -1,8 +1,38 @@
1<script lang="ts"> 1<script lang="ts">
2 export let data; 2import {afterUpdate} from 'svelte';
3 export let nofooter: boolean = false; 3export let data: Array<any> | undefined = undefined;
4 export let noheader: boolean = false; 4export let nofooter: boolean = false;
5 export let title: string; 5export let noheader: boolean = false;
6export let omit: string[] = ['id'];
7export let title: string | undefined = undefined;
8
9function getDataKeys(data: any[]): string[] {
10 if (!data || data.length === 0) return [];
11 return Object.keys(data[0]).map(k => k.split('_').join(' ')).filter(k => !omit.includes(k));
12}
13
14function getRow(row: Record<string, any>): Array<any> {
15 return Object.entries(row).filter(r => !omit.includes(r[0]));
16}
17
18const formatter = new Intl.DateTimeFormat('en', {
19 year: 'numeric',
20 month: 'numeric',
21 day: 'numeric',
22 hour: 'numeric',
23 minute: '2-digit',
24 second: '2-digit',
25 timeZone: "America/New_York"
26});
27
28function formatDatum([key, value]: any[]) {
29 if (key === 'date') {
30 const parsedDate = new Date(value);
31 return formatter.format(parsedDate);
32 }
33 return value;
34}
35
6</script> 36</script>
7<table> 37<table>
8 {#if title} 38 {#if title}
@@ -11,16 +41,27 @@
11 {#if !noheader} 41 {#if !noheader}
12 <thead> 42 <thead>
13 <tr> 43 <tr>
14 <th> 44 {#each getDataKeys(data) as header}
15 Data Header 45 <th>{header}</th>
16 </th> 46 {/each}
17 </tr> 47 </tr>
18 </thead> 48 </thead>
19 {/if} 49 {/if}
20 <tbody> 50 <tbody>
51 {#if data}
52 {#each data as row}
21 <tr> 53 <tr>
22 <td>Data</td> 54 {#each getRow(row) as datum}
55
56 <td>{formatDatum(datum)}</td>
57 {/each}
23 </tr> 58 </tr>
59 {/each}
60 {:else}
61 <tr>
62 There is not data.
63 </tr>
64 {/if}
24 </tbody> 65 </tbody>
25 {#if !nofooter} 66 {#if !nofooter}
26 <slot name="footer"> 67 <slot name="footer">
@@ -38,4 +79,8 @@ table {
38 margin: 8px; 79 margin: 8px;
39 border: solid 1px black; 80 border: solid 1px black;
40} 81}
82
83th {
84 text-transform: capitalize;
85}
41</style> 86</style>