diff options
Diffstat (limited to 'ping/frontend/src/lib/components/dashboard')
4 files changed, 396 insertions, 0 deletions
diff --git a/ping/frontend/src/lib/components/dashboard/RiskAnalysis.svelte b/ping/frontend/src/lib/components/dashboard/RiskAnalysis.svelte new file mode 100644 index 0000000..3844abc --- /dev/null +++ b/ping/frontend/src/lib/components/dashboard/RiskAnalysis.svelte @@ -0,0 +1,10 @@ +<h2>Analyse de risque</h2> +<i>tkt t safe c hardcodé chef</i> + +<style> + h2 { + font-weight: bold; + color: var(--text-lime); + font-size: 24px; + } +</style> diff --git a/ping/frontend/src/lib/components/dashboard/StockGraph.svelte b/ping/frontend/src/lib/components/dashboard/StockGraph.svelte new file mode 100644 index 0000000..beefed9 --- /dev/null +++ b/ping/frontend/src/lib/components/dashboard/StockGraph.svelte @@ -0,0 +1,136 @@ +<script lang="ts"> + import Chart from 'chart.js/auto'; + import { onMount } from 'svelte'; + import StockSelector from '../input/StockSelector.svelte'; + + async function fetchChartData(stock: string, startDate: string, endDate: string) { + const res = await fetch( + `/stocksapi/chart?query=${stock}&startDate=${startDate}&endDate=${endDate}&interval=${range}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + } + ); + return await res.json(); + } + + function updateGraph() { + fetchChartData(selectedStock, startDate, endDate).then((data) => { + if (chart) { + chart.destroy(); + chart = null; + } + + const { quotes, meta } = data; + + validRanges = meta.validRanges; + + const labels = quotes.map((item: any) => item.date.split('T')[0]); + const datasets = ['low', 'high', 'open', 'close'].map((key) => ({ + label: key.charAt(0).toUpperCase() + key.slice(1), + data: quotes.map((item: any) => item[key]), + borderColor: + key === 'low' + ? 'rgb(255, 99, 132)' + : key === 'high' + ? 'rgb(54, 162, 235)' + : key === 'open' + ? 'rgb(255, 205, 86)' + : 'rgb(75, 192, 192)', + fill: false + })); + + // @ts-ignore + chart = new Chart(document.getElementById('stockgraph'), { + type: 'line', + data: { + labels, + datasets + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + title: { + display: true, + text: meta.longName + } + } + } + }); + }); + } + + let today = $state(new Date().toISOString().split('T')[0]); + + let selectedStock = $state('2223.SR'); + let startDate = $state(''); + let endDate = $state(''); + let chart = $state<Chart | null>(null); + let range = $state('1d'); + let validRanges = $state(['1d']); + + onMount(() => { + endDate = new Date().toISOString().split('T')[0]; + startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; + + updateGraph(); + }); + + $effect(() => { + if (selectedStock && startDate && endDate && range) { + updateGraph(); + } + }); + + $effect(() => { + if (startDate && endDate && startDate > endDate) { + let temp = startDate; + startDate = endDate; + endDate = temp; + } + }); +</script> + +<div> + <div class="header"> + <h2>Vue d'ensemble : {selectedStock}</h2> + <div class="controls"> + <input type="date" name="startDate" id="startDate" bind:value={startDate} max={today} /> + <input type="date" name="endDate" id="endDate" bind:value={endDate} max={today} /> + <select name="validRanges" id="validRanges" bind:value={range}> + {#each validRanges as r} + <option value={r}> + {r} + </option> + {/each} + </select> + + <StockSelector bind:selectedStock /> + </div> + </div> + <canvas id="stockgraph"></canvas> +</div> + +<style> + .header { + display: flex; + align-items: center; + justify-content: space-between; + max-height: 512px !important; + } + + h2 { + font-weight: bold; + color: var(--text-lime); + font-size: 24px; + } + + #stockgraph { + width: 100% !important; + max-height: 512px !important; + } +</style> diff --git a/ping/frontend/src/lib/components/dashboard/TrendingSymbols.svelte b/ping/frontend/src/lib/components/dashboard/TrendingSymbols.svelte new file mode 100644 index 0000000..3d947ad --- /dev/null +++ b/ping/frontend/src/lib/components/dashboard/TrendingSymbols.svelte @@ -0,0 +1,85 @@ +<script lang="ts"> + import { Chart } from 'chart.js'; + import { onMount } from 'svelte'; + + function updateGraph() { + getTrendingSymbols().then((trendingSymbols) => { + const labels = trendingSymbols.map((d) => d.longName); + + const datasets = [ + { + label: '50 day performance', + data: trendingSymbols.map((ts) => ts.fiftyDayAverageChange), + backgroundColor: 'green' + }, + { + label: '52 week performance', + data: trendingSymbols.map((ts) => ts.fiftyTwoWeekLowChange), + backgroundColor: 'orange' + } + ]; + + const data = { labels, datasets }; + + // @ts-ignore + new Chart(document.getElementById('graph'), { + type: 'bar', + data: data, + options: { + indexAxis: 'y', + elements: { + bar: { + borderWidth: 2 + } + }, + responsive: true, + plugins: { + legend: { + position: 'bottom' + } + } + } + }); + }); + } + + async function getTrendingSymbols() { + const trendingRes = await fetch('/stocksapi/trendingSymbols'); + if (!trendingRes.ok) { + throw new Error('Failed to fetch trending symbols'); + } + const trendingJson = await trendingRes.json(); + + const { quotes } = trendingJson; + + const trendingSymbols = await Promise.all( + quotes.map(async (quote: any) => { + const quoteRes = await fetch(`/stocksapi/quote?query=${quote.symbol}`); + const quoteJson = await quoteRes.json(); + return quoteJson; + }) + ); + + return trendingSymbols; + } + + onMount(() => { + updateGraph(); + }); +</script> + +<h2>Tendances</h2> +<canvas id="graph"></canvas> + +<style> + h2 { + font-weight: bold; + color: var(--text-lime); + font-size: 24px; + } + + #graph { + max-height: 40vh; + max-width: 40vw; + } +</style> diff --git a/ping/frontend/src/lib/components/dashboard/transactions/TransactionModal.svelte b/ping/frontend/src/lib/components/dashboard/transactions/TransactionModal.svelte new file mode 100644 index 0000000..b9f0224 --- /dev/null +++ b/ping/frontend/src/lib/components/dashboard/transactions/TransactionModal.svelte @@ -0,0 +1,165 @@ +<script lang="ts"> + import { authFetch } from '$lib/stores/auth'; + import { onMount } from 'svelte'; + + interface IProps { + isOpen: boolean; + onCreate: (transaction: any) => void; + } + + let { isOpen = $bindable(false), onCreate = () => {} }: IProps = $props(); // Changed default to false + + let amount: number = $state(0); + let currency: string = $state('USD'); + let label: string = $state(''); + let receiverLabel: string = $state(''); + let receiverIban: string = $state(''); + let operationDate: string = $state(''); + + function closeDialog() { + isOpen = false; + } + + async function createTransaction(event: Event) { + event.preventDefault(); + + const transaction = { + amount, + currency, + label, + receiverLabel, + receiverIban, + operationDate + }; + + try { + const response = await authFetch('/api/transactions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(transaction) + }); + + if (!response.ok) { + const error = await response.json(); + alert('Erreur lors de la création de la transaction: ' + error); + return; + } + + onCreate(await response.json()); + + closeDialog(); + } catch (err) { + alert('Erreur réseau lors de la création de la transaction'); + } + } +</script> + +<!-- svelte-ignore a11y_click_events_have_key_events --> +<!-- svelte-ignore a11y_no_static_element_interactions --> +{#if isOpen} + <div class="backdrop" onclick={closeDialog}> + <dialog open onclick={(e) => e.stopPropagation()}> + <h2>Créer une nouvelle transaction</h2> + <form class="flex flex-col gap-2"> + <label> + <span>Montant</span> + <input type="number" step="0.01" min="0" bind:value={amount} required /> + </label> + <label> + <span>Devise</span> + <select bind:value={currency} required> + <option selected value="USD" data-symbol="$" data-name="Dollar américain" + >USD - Dollar américain</option + > + <option value="EUR" data-symbol="€" data-name="Euro">EUR - Euro</option> + <option value="GBP" data-symbol="£" data-name="Livre sterling" + >GBP - Livre sterling</option + > + <option value="JPY" data-symbol="¥" data-name="Yen japonais">JPY - Yen japonais</option> + <option value="CHF" data-symbol="Fr" data-name="Franc suisse">CHF - Franc suisse</option + > + <option value="CAD" data-symbol="$" data-name="Dollar canadien" + >CAD - Dollar canadien</option + > + <option value="AUD" data-symbol="$" data-name="Dollar australien" + >AUD - Dollar australien</option + > + <option value="CNY" data-symbol="¥" data-name="Yuan chinois">CNY - Yuan chinois</option> + </select> + </label> + <label> + <span>Libellé</span> + <input type="text" bind:value={label} required /> + </label> + <label> + <span>Libellé du bénéficiaire</span> + <input type="text" bind:value={receiverLabel} required /> + </label> + <label> + <span>IBAN du bénéficiaire</span> + <input type="text" bind:value={receiverIban} required /> + </label> + <label> + <span>Date de l'opération</span> + <input type="datetime-local" bind:value={operationDate} required /> + </label> + <button class="btn" onclick={createTransaction}>➕ Créer</button> + </form> + </dialog> + </div> +{/if} + +<style> + .backdrop { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + } + + dialog { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + max-width: 90vw; + max-height: 90vh; + border: none; + border-radius: 8px; + box-shadow: 0 2px 16px rgba(0, 0, 0, 0.2); + padding: 2rem; + background: var(--bg-primary); + outline: none; + display: flex; + flex-direction: column; + gap: 16px; + align-items: center; + justify-content: center; + color: white; + } + + h2 { + font-weight: 600; + font-size: 32px; + color: var(--text-lime); + } + + form { + display: flex; + flex-direction: column; + gap: 8px; + } + + label { + display: flex; + flex-direction: column; + } +</style> |
