From 967be9e750221ab2ab783f95df79bb26d290a45e Mon Sep 17 00:00:00 2001
From: Martial Simon <msimon_fr@hotmail.com>
Date: Mon, 15 Sep 2025 01:07:58 +0200
Subject: add: added projects

---
 .../src/lib/components/input/StockSelector.svelte  | 231 +++++++++++++++++++++
 1 file changed, 231 insertions(+)
 create mode 100644 ping/frontend/src/lib/components/input/StockSelector.svelte

(limited to 'ping/frontend/src/lib/components/input/StockSelector.svelte')

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>
-- 
cgit v1.2.3

