summaryrefslogtreecommitdiff
path: root/ping/frontend/src/lib/components/input
diff options
context:
space:
mode:
authorMartial Simon <msimon_fr@hotmail.com>2025-09-15 01:07:58 +0200
committerMartial Simon <msimon_fr@hotmail.com>2025-09-15 01:07:58 +0200
commit967be9e750221ab2ab783f95df79bb26d290a45e (patch)
tree6802900a5e975f9f68b169f0f503f040056d6952 /ping/frontend/src/lib/components/input
add: added projectsHEADmain
Diffstat (limited to 'ping/frontend/src/lib/components/input')
-rw-r--r--ping/frontend/src/lib/components/input/StockSelector.svelte231
-rw-r--r--ping/frontend/src/lib/components/input/UserSelector.svelte35
2 files changed, 266 insertions, 0 deletions
diff --git a/ping/frontend/src/lib/components/input/StockSelector.svelte b/ping/frontend/src/lib/components/input/StockSelector.svelte
new file mode 100644
index 0000000..1237128
--- /dev/null
+++ b/ping/frontend/src/lib/components/input/StockSelector.svelte
@@ -0,0 +1,231 @@
+<script lang="ts">
+ import { onMount } from 'svelte';
+ import Button from '../Button.svelte';
+
+ let { selectedStock = $bindable('AAPL') } = $props();
+
+ let stocks: {
+ symbol: string;
+ shortname: string;
+ quoteType: string;
+ isYahooFinance: boolean;
+ }[] = $state([]);
+ let news: {
+ link: string;
+ title: string;
+ thumbnail: { resolutions: { url: string; width: number; height: number }[] };
+ }[] = $state([]);
+
+ function openDialog() {
+ const dialog = document.querySelector('dialog');
+ const backdrop = document.getElementById('backdrop');
+ // @ts-ignore
+ dialog.open = true;
+ // @ts-ignore
+ backdrop.style.display = 'block';
+ }
+
+ function closeDialog() {
+ const dialog = document.querySelector('dialog');
+ const backdrop = document.getElementById('backdrop');
+ // @ts-ignore
+ dialog.open = false;
+ // @ts-ignore
+ backdrop.style.display = 'none';
+ }
+
+ function onSearch(event: SubmitEvent) {
+ event.preventDefault();
+ // @ts-ignore
+ const fd = new FormData(event.target);
+ const searchQuery = fd.get('search');
+
+ fetch(`/stocksapi/search?query=${searchQuery}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-cache'
+ }
+ })
+ .then((response) => response.json())
+ .then((data) => {
+ stocks = data.quotes.filter((q: any) => q.isYahooFinance);
+ news = data.news;
+ })
+ .catch((error) => {
+ console.error('Error fetching stock data:', error);
+ });
+ }
+
+ onMount(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ closeDialog();
+ }
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ });
+</script>
+
+<Button onclick={openDialog}>{selectedStock}</Button>
+
+<dialog>
+ <form onsubmit={onSearch}>
+ <input
+ type="search"
+ name="search"
+ id="search"
+ placeholder="Recherche d'actions, ETFs, entreprises"
+ value={selectedStock || ''}
+ />
+ <button type="submit" class="btn">Rechercher</button>
+ </form>
+ <div class="results">
+ <div class="stocks">
+ {#if stocks.length === 0}
+ <p>Aucune action, ETFs trouvés.</p>
+ {:else}
+ <p>{stocks.length} actions, ETFs trouvés</p>
+ {#each stocks as stock}
+ <button
+ class="btn stock"
+ onclick={() => {
+ selectedStock = stock.symbol;
+ closeDialog();
+ }}
+ disabled={!stock.isYahooFinance}
+ >
+ <pre>{stock.symbol}</pre>
+ <span>{stock.shortname}</span>
+ <i>{stock.quoteType}</i>
+ </button>
+ {/each}
+ {/if}
+ </div>
+ <div class="news">
+ {#if news.length === 0}
+ <p>Pas de news trouvés.</p>
+ {:else}
+ <p>{news.length} news trouvées</p>
+ {#each news as newsItem}
+ <a class="newsItem" href={newsItem.link}>
+ {#if newsItem.thumbnail?.resolutions?.length > 0}
+ <img
+ src={newsItem.thumbnail.resolutions[0].url}
+ alt="News Thumbnail"
+ width={newsItem.thumbnail.resolutions[0].width}
+ height={newsItem.thumbnail.resolutions[0].height}
+ />
+ {/if}
+ <h1>{newsItem.title}</h1></a
+ >
+ {/each}
+ {/if}
+ </div>
+ </div>
+</dialog>
+<!-- svelte-ignore a11y_click_events_have_key_events -->
+<!-- svelte-ignore a11y_no_static_element_interactions -->
+<div id="backdrop" onclick={closeDialog}></div>
+
+<style>
+ dialog {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 300px;
+ padding: 20px;
+ background-color: var(--bg-primary);
+ border: 4px solid #ccc;
+ border-radius: 16px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ height: 90vh;
+ width: 90vw;
+ z-index: 1000;
+ color: white;
+ }
+
+ #backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 999;
+ display: none;
+ }
+
+ form {
+ display: flex;
+ gap: 10px;
+ }
+
+ input[type='search'] {
+ width: 300px;
+ }
+
+ .results {
+ display: flex;
+ flex-direction: row;
+ gap: 20px;
+ justify-content: space-evenly;
+ }
+
+ .stocks,
+ .news {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ overflow-y: auto;
+ max-height: 70vh;
+ padding: 16px;
+ flex: 1;
+ }
+
+ .stock {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .stock pre {
+ font-weight: bold;
+ padding: 8px;
+ background: var(--bg-primary);
+ border-radius: 8px;
+ }
+
+ .stock span {
+ font-weight: bold;
+ color: var(--text-lime);
+ }
+
+ .newsItem {
+ display: flex;
+ flex-direction: column;
+ background-color: var(--bg-secondary);
+ padding-bottom: 4px;
+ border-radius: 8px;
+ transition-duration: 0.2s;
+ }
+
+ .newsItem h1 {
+ margin: 16px;
+ }
+
+ .newsItem img {
+ border-radius: 8px;
+ width: 100%;
+ height: auto;
+ }
+
+ .newsItem:hover {
+ transform: scale(1.025);
+ background-color: #333;
+ }
+</style>
diff --git a/ping/frontend/src/lib/components/input/UserSelector.svelte b/ping/frontend/src/lib/components/input/UserSelector.svelte
new file mode 100644
index 0000000..a0c80c7
--- /dev/null
+++ b/ping/frontend/src/lib/components/input/UserSelector.svelte
@@ -0,0 +1,35 @@
+<script lang="ts">
+ import type { IUser } from '$lib/stores/auth';
+ import { addToast } from '$lib/stores/toast';
+ import Avatar from '../Avatar.svelte';
+
+ let { users = $bindable<IUser[]>([]) }: { users: IUser[] } = $props();
+</script>
+
+<div>
+ {#each users as u, i}
+ <Avatar
+ username={u.displayName}
+ url={u.avatar}
+ onclick={() => {
+ users = users.filter((_, index) => index !== i);
+ }}
+ />
+ {/each}
+ <Avatar
+ username={'Ajouter un utilisateur'}
+ url={'/icons/add-green.svg'}
+ onclick={() => {
+ let userId = prompt("Entrez l'ID de l'utilisateur à ajouter :");
+ if (userId === null || userId.trim() === '') {
+ addToast({ title: 'ID utilisateur invalide.' });
+ return;
+ }
+ if (users.map((u) => u.displayName).includes(userId)) {
+ addToast({ title: 'Cet utilisateur est déjà ajouté.' });
+ return;
+ }
+ users = [...users, { displayName: userId, avatar: '/img/default-avatar.png' } as IUser];
+ }}
+ />
+</div>