aboutsummaryrefslogtreecommitdiff
path: root/fe
diff options
context:
space:
mode:
authorZach Berwaldt <zberwaldt@tutamail.com>2024-03-02 16:52:55 -0500
committerZach Berwaldt <zberwaldt@tutamail.com>2024-03-02 16:52:55 -0500
commitcf2113e77edabf8e3a632c7b76c769752039ba88 (patch)
tree874872f22aa63df532769de62119816748b167f8 /fe
parent326f186d67017f87e631a1fbcdf3f184cbc42d7d (diff)
feat: Add API logging
Add logging to the API to keep track of specific requests and headers. Also added CORS middleware to handle OPTIONS requests. --- The commit adds logging functionality to the API and includes a middleware function to handle CORS OPTIONS requests. This will allow us to track specific requests and headers received by the API. [API/main.go](/api/main.go): - Added import for the 'log' package - Added logging statements to print the request headers and "_I am here_" message - Removed unnecessary newlines and comments [fe/src/app.css](/fe/src/app.css): - Added a new style for button hover effects [fe/src/lib/Card.svelte](/fe/src/lib/Card.svelte): - Added a new `height` prop to the Card component [fe/src/lib/Column.svelte](/fe/src/lib/Column.svelte): - Added a new CSS class for column layout - Set the width and gap using CSS variables [fe/src/lib/DataView.svelte](/fe/src/lib/DataView.svelte): - Updated the 'fetchData' function to also fetch 'totals' and 'userStats' data - Added canvas references and chart variables for bar and line charts - Added a new 'getLastSevenDays' function to calculate the labels for the charts - Updated the 'onMount' function to initialize the bar and line charts using the canvas references and data - Updated the 'onDestroy' function to destroy the bar and line charts - Added a new 'addFormOpen' store and imported it - Added a new 'onClick' handler for the Add button to open the AddForm modal - Updated the layout and added Card components to display the bar and line charts and the JSON data - Added a new 'fetchTotals' function to fetch data for the 'totals' section - Added a new 'fetchStatsForUser' function to fetch data for the 'userStats' section [fe/src/lib/Layout.svelte](/fe/src/lib/Layout.svelte): - Added a new 'preferenceFormOpen' variable and initialized it to 'false' - Added a new 'showPreferencesDialog' function to set 'preferenceFormOpen' to 'true' - Added a new 'closePreferenceDialog' function to set 'preferenceFormOpen' to 'false' - Added a new 'showAddDialog' function to open the AddForm modal
Diffstat (limited to 'fe')
-rw-r--r--fe/src/app.css101
-rw-r--r--fe/src/lib/Card.svelte30
-rw-r--r--fe/src/lib/Column.svelte13
-rw-r--r--fe/src/lib/DataView.svelte129
-rw-r--r--fe/src/lib/Layout.svelte116
-rw-r--r--fe/src/lib/Table.svelte172
-rw-r--r--fe/src/stores/forms.ts6
7 files changed, 345 insertions, 222 deletions
diff --git a/fe/src/app.css b/fe/src/app.css
index 0d5fa90..de19b52 100644
--- a/fe/src/app.css
+++ b/fe/src/app.css
@@ -1,82 +1,87 @@
1:root { 1:root {
2 font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 2 font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 line-height: 1.5; 3 line-height: 1.5;
4 font-weight: 400; 4 font-weight: 400;
5 5
6 color-scheme: light dark; 6 color-scheme: light dark;
7 color: rgba(255, 255, 255, 0.87); 7 color: rgba(255, 255, 255, 0.87);
8 background-color: #242424; 8 background-color: #242424;
9 9
10 font-synthesis: none; 10 font-synthesis: none;
11 text-rendering: optimizeLegibility; 11 text-rendering: optimizeLegibility;
12 -webkit-font-smoothing: antialiased; 12 -webkit-font-smoothing: antialiased;
13 -moz-osx-font-smoothing: grayscale; 13 -moz-osx-font-smoothing: grayscale;
14 14
15 --submit: #28a745; 15 --submit: #28a745;
16} 16}
17 17
18a { 18a {
19 font-weight: 500; 19 font-weight: 500;
20 color: #646cff; 20 color: #646cff;
21 text-decoration: inherit; 21 text-decoration: inherit;
22} 22}
23
23a:hover { 24a:hover {
24 color: #535bf2; 25 color: #535bf2;
25} 26}
26 27
27body { 28body {
28 margin: 0; 29 margin: 0;
29 display: flex; 30 display: flex;
30 place-items: center; 31 place-items: center;
31 min-width: 320px; 32 min-width: 320px;
32 min-height: 100vh; 33 min-height: 100vh;
33} 34}
34 35
35h1 { 36h1 {
36 font-size: 3.2em; 37 font-size: 3.2em;
37 line-height: 1.1; 38 line-height: 1.1;
38} 39}
39 40
40.card { 41.card {
41 padding: 2em; 42 padding: 2em;
42} 43}
43 44
44#app { 45#app {
45 flex-grow: 2; 46 flex-grow: 2;
46 max-width: 1280px; 47 max-width: 1280px;
47 margin: 0 auto; 48 margin: 0 auto;
48} 49}
49 50
50button { 51button {
51 border-radius: 8px; 52 border-radius: 8px;
52 border: 1px solid transparent; 53 border: 1px solid transparent;
53 padding: 0.6em 1.2em; 54 padding: 0.6em 1.2em;
54 font-size: 1em; 55 font-size: 1em;
55 font-weight: 500; 56 font-weight: 500;
56 font-family: inherit; 57 font-family: inherit;
57 background-color: #1a1a1a; 58 background-color: #1a1a1a;
58 cursor: pointer; 59 cursor: pointer;
59 transition: border-color 0.25s; 60 transition: border-color 0.25s;
60} 61}
62
61button:hover { 63button:hover {
62 border-color: #646cff; 64 border-color: #646cff;
63} 65}
66
64button:focus, 67button:focus,
65button:focus-visible { 68button:focus-visible {
66 outline: 4px auto -webkit-focus-ring-color; 69 outline: 4px auto -webkit-focus-ring-color;
67} 70}
68 71
69@media (prefers-color-scheme: light) { 72@media (prefers-color-scheme: light) {
70 :root { 73 :root {
71 color: #213547; 74 color: #213547;
72 background-color: #ffffff; 75 background-color: #ffffff;
73 } 76 }
74 a:hover { 77
75 color: #747bff; 78 a:hover {
76 } 79 color: #747bff;
77 button { 80 }
78 background-color: #f9f9f9; 81
79 } 82 button {
83 background-color: #f9f9f9;
84 }
80} 85}
81 86
82@media (prefers-color-scheme: dark) { 87@media (prefers-color-scheme: dark) {
@@ -97,7 +102,7 @@ button:focus-visible {
97} 102}
98 103
99.form.input.group label { 104.form.input.group label {
100 margin-bottom: .5em; 105 margin-bottom: .5em;
101} 106}
102 107
103.form.input.group input { 108.form.input.group input {
diff --git a/fe/src/lib/Card.svelte b/fe/src/lib/Card.svelte
index 0835940..d7cd900 100644
--- a/fe/src/lib/Card.svelte
+++ b/fe/src/lib/Card.svelte
@@ -1,22 +1,24 @@
1<script lang="ts"> 1<script lang="ts">
2 export let title = ""; 2 export let title = "";
3 export let height: number | undefined = undefined;
3</script> 4</script>
4 5
5<div class="card"> 6<div class="card">
6 {#if title} 7 {#if title}
7 <h2>{title}</h2> 8 <h2>{title}</h2>
8 {/if} 9 {/if}
9 <slot /> 10 <slot />
10</div> 11</div>
11 12
12<style> 13<style>
13 .card { 14 .card {
14 background: #fff; 15 background: #fff;
15 width: 16rem; 16 display: flex;
16 display: flex; 17 flex-direction: column;
17 flex-direction: column; 18 align-items: flex-start;
18 align-items: left; 19 border: solid 2px #00000066;
19 border: solid 2px #00000066; 20 border-radius: 0.25em;
20 border-radius: 0.25em; 21 height: var(--height, fit-content);
21 } 22 overflow-y: var(--overflow, initial);
23 }
22</style> 24</style>
diff --git a/fe/src/lib/Column.svelte b/fe/src/lib/Column.svelte
new file mode 100644
index 0000000..f036073
--- /dev/null
+++ b/fe/src/lib/Column.svelte
@@ -0,0 +1,13 @@
1<div class="column">
2 <slot />
3</div>
4
5<style>
6 .column {
7 display: flex;
8 flex-direction: column;
9 height: 100%;
10 gap: var(--gap, 32px);
11 width: var(--width, initial);
12 }
13</style> \ No newline at end of file
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte
index 5182a85..2b1b8b9 100644
--- a/fe/src/lib/DataView.svelte
+++ b/fe/src/lib/DataView.svelte
@@ -1,16 +1,21 @@
1<script lang="ts"> 1<script lang="ts">
2 import { onDestroy, onMount } from "svelte"; 2 import { onDestroy, onMount } from "svelte";
3 import { token } from "../stores/auth"; 3 import { token } from "../stores/auth";
4 import { addFormOpen } from "../stores/forms";
4 import Table from "./Table.svelte"; 5 import Table from "./Table.svelte";
5 import Chart from "chart.js/auto"; 6 import Chart from "chart.js/auto";
7 import Card from "./Card.svelte";
8 import Column from "./Column.svelte";
6 import AddForm from "./forms/AddForm.svelte"; 9 import AddForm from "./forms/AddForm.svelte";
7 10
8 let open: boolean = false;
9
10 let json: Promise<any>; 11 let json: Promise<any>;
12 let totals: Promise<any>;
13 let userStats: Promise<any>;
11 14
12 let canvasRef: HTMLCanvasElement; 15 let barCanvasRef: HTMLCanvasElement;
13 let chart: any; 16 let lineCanvasRef: HTMLCanvasElement;
17 let barChart: any;
18 let lineChart: any;
14 19
15 let lastSevenDays: string[]; 20 let lastSevenDays: string[];
16 21
@@ -28,6 +33,37 @@
28 } 33 }
29 } 34 }
30 35
36 async function fetchTotals() {
37 const res = await fetch("http://localhost:8080/api/v1/stats/totals/", {
38 method: 'GET',
39 mode: 'no-cors',
40 headers: {
41 Authorization: `Bearer ${$token}`
42 }
43 });
44
45 if (res.ok) {
46 totals = res.json();
47 } else {
48 throw new Error("There was a problem with your request");
49 }
50 }
51
52 async function fetchStatsForUser() {
53 const res = await fetch("http://localhost:8080/api/v1/stats/user/1aa668f3-7527-4a67-9c24-fdf307542eeb", {
54 method: "GET",
55 headers: {
56 Authorization: `Bearer ${$token}`
57 }
58 });
59
60 if (res.ok) {
61 userStats = res.json();
62 } else {
63 throw new Error("There was a problem with your request");
64 }
65 }
66
31 function getLastSevenDays() { 67 function getLastSevenDays() {
32 const result = []; 68 const result = [];
33 for (let i = 0; i < 7; i++) { 69 for (let i = 0; i < 7; i++) {
@@ -38,23 +74,21 @@
38 return result; 74 return result;
39 } 75 }
40 76
41 function handleClick() {
42 open = true;
43 }
44
45 function closeDialog() { 77 function closeDialog() {
46 open = false; 78 addFormOpen.set(false);
47 } 79 }
48 80
49 function onStatisticAdd() { 81 function onStatisticAdd() {
50 open = false; 82 closeDialog();
51 fetchData(); 83 fetchData();
52 } 84 }
53 85
54 onMount(() => { 86 onMount(() => {
55 fetchData(); 87 fetchData();
88// fetchTotals();
89 fetchStatsForUser();
56 lastSevenDays = getLastSevenDays(); 90 lastSevenDays = getLastSevenDays();
57 chart = new Chart(canvasRef, { 91 barChart = new Chart(barCanvasRef, {
58 type: "bar", 92 type: "bar",
59 data: { 93 data: {
60 labels: lastSevenDays, 94 labels: lastSevenDays,
@@ -73,27 +107,74 @@
73 borderWidth: 1 107 borderWidth: 1
74 } 108 }
75 ] 109 ]
110 },
111 options: {
112 responsive: true
113 }
114 });
115 lineChart = new Chart(lineCanvasRef, {
116 type: "line",
117 data: {
118 labels: lastSevenDays,
119 datasets: [
120 {
121 label: "Zach",
122 data: [1, 2, 8, 2, 5, 5, 1],
123 backgroundColor: "rgba(255, 192, 192, 0.2)",
124 borderColor: "rgba(75, 192, 192, 1)",
125 borderWidth: 1
126 }, {
127 label: "Parker",
128 data: [6, 1, 1, 4, 3, 5, 1],
129 backgroundColor: "rgba(75, 192, 192, 0.2)",
130 borderColor: "rgba(75, 192, 192, 1)",
131 borderWidth: 1
132 }
133 ]
134 },
135 options: {
136 responsive: true
76 } 137 }
77 }); 138 });
78 }); 139 });
79 140
80 onDestroy(() => { 141 onDestroy(() => {
81 if (chart) chart.destroy(); 142 if (barChart) barChart.destroy();
82 chart = null; 143 if (lineChart) lineChart.destroy();
144 barChart = null;
145 lineChart = null;
83 }); 146 });
84</script> 147</script>
85 148
86<div> 149<Column --width="500px">
87 <button on:click={handleClick}>Add</button> 150 <Card>
88 <AddForm {open} on:submit={onStatisticAdd} on:close={closeDialog} /> 151 <canvas bind:this={barCanvasRef} width="" />
89 <canvas bind:this={canvasRef} /> 152 </Card>
90 {#await json then data} 153 <Card>
91 <Table {data} nofooter /> 154 <canvas bind:this={lineCanvasRef} />
92 {:catch error} 155 </Card>
93 <p>{error}</p> 156</Column>
94 {/await} 157
95 <!-- <Chart /> --> 158<AddForm open={$addFormOpen} on:submit={onStatisticAdd} on:close={closeDialog} />
96</div> 159<Column>
160 <Card>
161 {#await json then data}
162 <Table {data} nofooter />
163 {:catch error}
164 <p>{error}</p>
165 {/await}
166 </Card>
167 <Card>
168 <button on:click={() => fetchTotals()}>Get totals</button>
169 {#await totals then data}
170 {JSON.stringify(data)}
171 {:catch error}
172 <p>{error}</p>
173 {/await}
174 </Card>
175</Column>
176<!-- <Chart /> -->
177
97 178
98<style> 179<style>
99 dialog { 180 dialog {
diff --git a/fe/src/lib/Layout.svelte b/fe/src/lib/Layout.svelte
index 94ce84d..f208f34 100644
--- a/fe/src/lib/Layout.svelte
+++ b/fe/src/lib/Layout.svelte
@@ -1,62 +1,70 @@
1<script> 1<script>
2 import { authenticated, token } from "../stores/auth"; 2 import { authenticated, token } from "../stores/auth";
3 import PreferencesForm from "./PreferencesForm.svelte"; 3 import PreferencesForm from "./PreferencesForm.svelte";
4 const logout = () => token.unauthenticate(); 4 import { addFormOpen } from "../stores/forms";
5 let open = false; 5
6 6 const logout = () => token.unauthenticate();
7 function showSettingsDialog() { 7 let preferenceFormOpen = false;
8 open = true; 8
9 } 9 function showPreferencesDialog() {
10 10 preferenceFormOpen = true;
11 function closeDialog() { 11 }
12 open = false; 12
13 } 13 function closePreferenceDialog() {
14 preferenceFormOpen = false;
15 }
16
17 function showAddDialog() {
18 addFormOpen.set(true);
19 }
14</script> 20</script>
15 21
16<div class="layout"> 22<div class="layout">
17 {#if $authenticated} 23 {#if $authenticated}
18 <nav> 24 <nav>
19 <div> 25 <div>
20 <h1>Water</h1> 26 <h1>Water</h1>
21 </div> 27 </div>
22 <div> 28 <div>
23 <button on:click={showSettingsDialog}>Settings</button> 29 <button on:click={showAddDialog}>Log Water</button>
24 <button on:click={logout}>Logout</button> 30 <button on:click={showPreferencesDialog}>Preference</button>
25 </div> 31 <button on:click={logout}>Logout</button>
26 </nav> 32 </div>
27 <PreferencesForm {open} on:close={closeDialog} /> 33 </nav>
28 {/if} 34 <PreferencesForm open={preferenceFormOpen} on:close={closePreferenceDialog} />
29 <div id="content"> 35 {/if}
30 <slot /> 36 <div id="content">
31 </div> 37 <slot />
38 </div>
32</div> 39</div>
33 40
34<style> 41<style>
35 .layout { 42 .layout {
36 height: 100vh; 43 height: 100vh;
37 } 44 }
38 nav { 45
39 display: flex; 46 nav {
40 flex-direction: row; 47 display: flex;
41 align-items: center; 48 flex-direction: row;
42 justify-content: space-between; 49 align-items: center;
43 height: 64px; 50 justify-content: space-between;
44 padding: 0 2em; 51 height: 64px;
45 } 52 padding: 0 2em;
46 53 }
47 nav div { 54
48 width: fit-content; 55 nav div {
49 } 56 width: fit-content;
50 57 }
51 nav div h1 { 58
52 font-size: 1.75em; 59 nav div h1 {
53 } 60 font-size: 1.75em;
54 61 }
55 #content { 62
56 display: flex; 63 #content {
57 flex-direction: column; 64 display: flex;
58 justify-content: center; 65 flex-direction: row;
59 align-items: center; 66 justify-content: center;
60 padding: 3em 0; 67 gap: 2em;
61 } 68 margin-top: 4em;
69 }
62</style> 70</style>
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte
index 3a66e0d..d1cd7da 100644
--- a/fe/src/lib/Table.svelte
+++ b/fe/src/lib/Table.svelte
@@ -1,105 +1,113 @@
1<script lang="ts"> 1<script lang="ts">
2 export let data: Array<any> | undefined = undefined; 2 export let data: Array<any> | undefined = undefined;
3 export let nofooter: boolean = false; 3 export let nofooter: boolean = false;
4 export let noheader: boolean = false; 4 export let noheader: boolean = false;
5 export let omit: string[] = ["id"]; 5 export let omit: string[] = ["id"];
6 export let title: string | undefined = undefined; 6 export let title: string | undefined = undefined;
7 7
8 function getDataKeys(data: any[]): string[] { 8 function getDataKeys(data: any[]): string[] {
9 if (!data || data.length === 0) return []; 9 if (!data || data.length === 0) return [];
10 return Object.keys(data[0]) 10 return Object.keys(data[0])
11 .map((k) => k.split("_").join(" ")) 11 .map((k) => k.split("_").join(" "))
12 .filter((k) => !omit.includes(k)); 12 .filter((k) => !omit.includes(k));
13 } 13 }
14 14
15 function getRow(row: Record<string, any>): Array<any> { 15 function getRow(row: Record<string, any>): Array<any> {
16 return Object.entries(row).filter((r) => !omit.includes(r[0])); 16 return Object.entries(row).filter((r) => !omit.includes(r[0]));
17 } 17 }
18 18
19 const formatter = new Intl.DateTimeFormat("en", {
20 year: "numeric",
21 month: "numeric",
22 day: "numeric",
23 hour: "numeric",
24 minute: "2-digit",
25 second: "2-digit",
26 timeZone: "America/New_York",
27 });
28 19
29 function formatDatum([key, value]: any[]) { 20 let limitedData: Array<any> = [];
30 if (key === "date") {
31 const parsedDate = new Date(value);
32 return formatter.format(parsedDate);
33 }
34 21
35 if (key === "user") { 22 if (data && (data as any[]).length > 0) {
36 return value["name"]; 23 limitedData = (data as any[]).slice(0, 4);
37 } 24 }
38 25
39 return value; 26 const formatter = new Intl.DateTimeFormat("en", {
40 } 27 year: "numeric",
28 month: "numeric",
29 day: "numeric",
30 hour: "numeric",
31 minute: "2-digit",
32 second: "2-digit",
33 timeZone: "America/New_York"
34 });
35
36 function formatDatum([key, value]: any[]) {
37 if (key === "date") {
38 const parsedDate = new Date(value);
39 return formatter.format(parsedDate);
40 }
41
42 if (key === "user") {
43 return value["name"];
44 }
45
46 return value;
47 }
41</script> 48</script>
42 49
43<table> 50<table>
44 {#if title} 51 {#if title}
45 <h2>{title}</h2> 52 <h2>{title}</h2>
46 {/if} 53 {/if}
47 {#if !noheader && data} 54 {#if !noheader && data}
48 <thead> 55 <thead>
49 <tr>
50 {#each getDataKeys(data) as header}
51 <th>{header}</th>
52 {/each}
53 </tr>
54 </thead>
55 {/if}
56 <tbody>
57 {#if data}
58 {#each data as row}
59 <tr> 56 <tr>
60 {#each getRow(row) as datum} 57 {#each getDataKeys(data) as header}
61 <td>{formatDatum(datum)}</td> 58 <th>{header}</th>
62 {/each} 59 {/each}
63 </tr> 60 </tr>
64 {/each} 61 </thead>
62 {/if}
63 <tbody>
64 {#if data}
65 {#each limitedData as row}
66 <tr>
67 {#each getRow(row) as datum}
68 <td>{formatDatum(datum)}</td>
69 {/each}
70 </tr>
71 {/each}
65 {:else} 72 {:else}
66 <tr> There is not data. </tr> 73 <tr> There is not data.</tr>
74 {/if}
75 </tbody>
76 {#if !nofooter}
77 <slot name="footer">
78 <tfoot>
79 <tr>
80 <td>Table Footer</td>
81 </tr>
82 </tfoot>
83 </slot>
67 {/if} 84 {/if}
68 </tbody>
69 {#if !nofooter}
70 <slot name="footer">
71 <tfoot>
72 <tr>
73 <td>Table Footer</td>
74 </tr>
75 </tfoot>
76 </slot>
77 {/if}
78</table> 85</table>
79 86
80<style> 87<style>
81 table { 88 table {
82 padding: 16px; 89 padding: 16px;
83 margin: 8px; 90 margin: 8px;
84 border: solid 1px black; 91 border: solid 1px black;
85 border-collapse: collapse; 92 border-collapse: collapse;
86 } 93 overflow-y: hidden;
94 }
87 95
88 th { 96 th {
89 text-transform: capitalize; 97 text-transform: capitalize;
90 } 98 }
91 99
92 thead tr { 100 thead tr {
93 background: rgba(0, 0, 23, 0.34); 101 background: rgba(0, 0, 23, 0.34);
94 } 102 }
95 103
96 tbody tr:nth-child(odd) { 104 tbody tr:nth-child(odd) {
97 background: rgba(0, 0, 23, 0.14); 105 background: rgba(0, 0, 23, 0.14);
98 } 106 }
99 107
100 th, 108 th,
101 td { 109 td {
102 padding: 1em; 110 padding: 1em;
103 border: 1px solid rgba(0, 0, 0, 1); 111 border: 1px solid rgba(0, 0, 0, 1);
104 } 112 }
105</style> 113</style>
diff --git a/fe/src/stores/forms.ts b/fe/src/stores/forms.ts
new file mode 100644
index 0000000..daf9181
--- /dev/null
+++ b/fe/src/stores/forms.ts
@@ -0,0 +1,6 @@
1import type { Writable } from "svelte/store";
2import { writable } from "svelte/store";
3
4
5export const preferencesFormOpen: Writable<boolean> = writable<boolean>(false);
6export const addFormOpen: Writable<boolean> = writable<boolean>(false); \ No newline at end of file