diff options
| author | Martial Simon <msimon_fr@hotmail.com> | 2025-09-15 01:07:58 +0200 |
|---|---|---|
| committer | Martial Simon <msimon_fr@hotmail.com> | 2025-09-15 01:07:58 +0200 |
| commit | 967be9e750221ab2ab783f95df79bb26d290a45e (patch) | |
| tree | 6802900a5e975f9f68b169f0f503f040056d6952 /ping/frontend/src/routes/dashboard/models | |
Diffstat (limited to 'ping/frontend/src/routes/dashboard/models')
| -rw-r--r-- | ping/frontend/src/routes/dashboard/models/+page.svelte | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/ping/frontend/src/routes/dashboard/models/+page.svelte b/ping/frontend/src/routes/dashboard/models/+page.svelte new file mode 100644 index 0000000..3be6f00 --- /dev/null +++ b/ping/frontend/src/routes/dashboard/models/+page.svelte @@ -0,0 +1,568 @@ +<script lang="ts"> + import Avatar from '$lib/components/Avatar.svelte'; + import UserSelector from '$lib/components/input/UserSelector.svelte'; + import { authFetch, getUser, type IUser } from '$lib/stores/auth'; + import { addToast } from '$lib/stores/toast'; + import { onMount } from 'svelte'; + import { get } from 'svelte/store'; + + interface IModel { + id: string; + members: IUser[]; + name: string; + owner: IUser; + } + + let modelName = $state(''); + let sources = $state([]); + let models = $state<IModel[]>([]); + let modelToEdit = $state<number | null>(null); + let modelOwner = $state<IUser | null>(null); + + let users = $state<IUser[]>([]); + + function addSource() { + sources = [...sources, '']; + } + + function removeSource(index: number) { + sources = sources.filter((_, i) => i !== index); + } + + function handleSubmit(e: SubmitEvent) { + e.preventDefault(); + if (modelToEdit === null) { + createModel(); + } else { + updateModel(); + } + } + + async function createModel() { + try { + const response = await authFetch('/api/projects', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + accept: 'application/json' + }, + body: JSON.stringify({ + name: modelName + }) + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + models = [...models, data]; + await Promise.all( + users.map((user) => + authFetch(`/api/projects/${data.id}/add-user`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + accept: '*/*' + }, + body: JSON.stringify({ userId: user.id }) + }) + .then((res) => { + if (!res.ok) { + addToast({ + color: 'red', + title: 'Erreur', + message: `Impossible d'ajouter l'utilisateur ${user.displayName} au modèle "${modelName}".` + }); + console.error(`HTTP error! status: ${res.status}`); + } + return res.json().catch(() => null); + }) + .then((memberData) => { + console.log('Member added:', memberData); + }) + .catch((err) => { + console.error('Error adding member:', err); + }) + ) + ); + addToast({ + color: 'green', + title: 'Modèle créé', + message: `Le modèle "${modelName}" a été créé avec succès.` + }); + modelName = ''; + return data; + } catch (error) { + console.error('Error creating model:', error); + addToast({ + color: 'red', + title: 'Erreur', + message: `Une erreur est survenue lors de la création du modèle "${modelName}".` + }); + throw error; + } + } + + async function updateModel() { + try { + if (modelToEdit === null || models.length <= modelToEdit) { + addToast({ + color: 'red', + title: 'Erreur', + message: 'Aucun modèle sélectionné pour la mise à jour.' + }); + return; + } + const model = models[modelToEdit]; + console.log('Updating model:', model); + const response = await authFetch(`/api/projects/${model.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + accept: 'application/json' + }, + body: JSON.stringify({ + name: modelName + }) + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const updatedModel = await response.json(); + models = models.map((m, i) => (i === modelToEdit ? updatedModel : m)); + modelToEdit = null; + modelName = ''; + addToast({ + color: 'green', + title: 'Modèle mis à jour', + message: `Le modèle "${updatedModel.name}" a été mis à jour avec succès.` + }); + } catch (error) { + console.error('Error updating model:', error); + addToast({ + color: 'red', + title: 'Erreur', + message: `Une erreur est survenue lors de la mise à jour du modèle "${modelName}".` + }); + throw error; + } + } + + async function getMyModels() { + try { + const response = await authFetch('/api/projects', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + accept: 'application/json' + } + }); + const data = await response.json(); + console.log('My models:', data); + return data; + } catch (error) { + console.error('Error fetching models:', error); + addToast({ + color: 'red', + title: 'Erreur', + message: 'Impossible de charger les modèles.' + }); + throw error; + } + } + + function deleteModel(i: number) { + if (models.length <= i || i < 0) { + addToast({ + color: 'red', + title: 'Erreur', + message: 'Modèle introuvable.' + }); + return; + } + + modelToEdit = null; + + const modelToDelete = models[i]; + authFetch(`/api/projects/${modelToDelete.id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + accept: 'application/json' + } + }) + .then(() => { + models = models.filter((_, index) => index !== i); + addToast({ + color: 'green', + title: 'Modèle supprimé', + message: `Le modèle "${modelToDelete.name}" a été supprimé avec succès.` + }); + }) + .catch((err) => { + console.error('Error deleting model:', err); + addToast({ + color: 'red', + title: 'Erreur', + message: `Impossible de supprimer le modèle "${modelToDelete.name}".` + }); + }); + } + + function onEditButtonPressed(i: number) { + if (modelToEdit === i) { + modelToEdit = null; + modelName = ''; + users = []; + modelOwner = null; + } else { + modelToEdit = i; + modelName = models[i].name; + users = models[i].members || []; + modelOwner = models[i].owner || null; + } + } + + let currentUser = $state<IUser | null>(null); + + onMount(() => { + getMyModels() + .then((lsModels) => { + models = lsModels; + console.log('Models loaded:', models); + }) + .catch((err) => { + console.error('Error loading models:', err); + }); + getUser() + .then((user) => { + currentUser = user; + console.log('Current user:', currentUser); + }) + .catch((err) => { + console.error('Error fetching current user:', err); + }); + }); +</script> + +<section id="models"> + <div class="models-container"> + <div class="active-models"> + <h2>Modèles actifs</h2> + <div class="models-list"> + {#each models as m, i} + <div class="model-item" class:selected={modelToEdit === i}> + <div class="model-name"> + <span>{m.name}</span><br /> + <code style="font-size:8px">{m.id}</code> + </div> + <div class="model-metrics"> + <div class="metric"> + <span class="metric-label">Eco score</span> + <span class="metric-value green">{69} %</span> + </div> + <div class="metric"> + <span class="metric-label">Efficacité</span> + <span class="metric-value green">{69} %</span> + </div> + </div> + <button class="modify-btn" onclick={() => onEditButtonPressed(i)}>✏️</button> + <button class="delete-btn btn" onclick={() => deleteModel(i)}>🗑️</button> + </div> + {/each} + </div> + </div> + + <div class="new-model"> + <form onsubmit={handleSubmit}> + <div class="new-model-header"> + {#if modelToEdit !== null} + <h2>Modifier un modèle</h2> + {:else} + <h2>Nouveau modèle</h2> + {/if} + <button class="register-btn" type="submit">📁 Enregistrer</button> + </div> + <div class="form-group"> + <label for="name">Nom du modèle</label> + <input type="text" id="name" bind:value={modelName} placeholder="Modèle X" /> + <div class="flex gap-2 p-2"> + {#if modelToEdit !== null} + <Avatar username={modelOwner?.displayName} url={modelOwner?.avatar} /> + {:else if currentUser} + <Avatar username={currentUser.displayName} url={currentUser.avatar} /> + {:else} + Chargement en cours... + {/if} + <UserSelector bind:users /> + </div> + </div> + + <div class="sources-section"> + <div class="sources-header"> + <span>Sources de données</span> + <button type="button" class="add-source-btn" onclick={addSource}> + ➕ Ajouter une source de données + </button> + </div> + + {#each sources as source, index} + <div class="source-item"> + <!-- svelte-ignore a11y_label_has_associated_control --> + <label>Source {index + 1}</label> + <div class="source-input-group"> + <input type="text" bind:value={sources[index]} placeholder="Source" /> + <button type="button" class="remove-source-btn" onclick={() => removeSource(index)}> + 🗑️ + </button> + </div> + </div> + {/each} + </div> + </form> + </div> + </div> +</section> + +<style> + #models { + padding: 16px; + background-color: var(--bg-secondary); + color: white; + min-height: calc(100vh - 72px); + } + + .models-container { + display: flex; + gap: 16px; + max-width: 1400px; + } + + h2 { + color: var(--text-lime); + font-size: 24px; + margin: 0 0 16px 0; + font-weight: 600; + } + + /* Modèles actifs */ + .active-models { + flex: 1; + background-color: var(--bg-primary); + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 16px; + margin: 16px; + color: white; + } + + .models-list { + display: flex; + flex-direction: column; + gap: 15px; + } + + .model-item { + background-color: var(--bg-secondary); + padding: 16px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: space-between; + border: 2px solid transparent; + } + + .model-item.selected { + border: 2px solid var(--text-lime); + } + + .model-name { + font-weight: 500; + font-size: 16px; + color: white; + } + + .model-metrics { + display: flex; + gap: 30px; + } + + .metric { + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + } + + .metric-label { + font-size: 12px; + color: #888888; + } + + .metric-value { + font-size: 18px; + font-weight: bold; + } + + .metric-value.green { + color: var(--text-lime); + } + + .modify-btn { + background-color: var(--btn-primary); + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 600; + transition-duration: 0.2s; + } + + .modify-btn:hover { + background-color: var(--btn-primary-hover); + transform: scale(1.025); + } + + /* Nouveau modèle */ + .new-model { + flex: 1; + background-color: var(--bg-primary); + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 16px; + margin: 16px; + color: white; + } + + .new-model-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + } + + .register-btn { + background-color: var(--btn-primary); + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-weight: 600; + font-size: 14px; + transition-duration: 0.2s; + } + + .register-btn:hover { + background-color: var(--btn-primary-hover); + transform: scale(1.025); + } + + .form-group { + margin-bottom: 16px; + } + + label { + display: block; + margin-bottom: 8px; + font-size: 14px; + color: white; + font-weight: 500; + } + + input[type='text'] { + width: 100%; + background-color: var(--bg-secondary); + border: none; + border-bottom: 2px solid var(--text-lime); + padding: 8px; + border-radius: 8px 8px 0 0; + color: white; + font-size: 14px; + box-sizing: border-box; + } + + input[type='text']:focus { + outline: none; + border-bottom-color: var(--text-lime); + } + + input[type='text']::placeholder { + color: #888888; + } + + /* Sources section */ + .sources-section { + margin-top: 16px; + } + + .sources-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + } + + .sources-header span { + font-size: 14px; + font-weight: 500; + color: white; + } + + .add-source-btn { + background-color: var(--btn-secondary); + color: var(--text-lime); + border: 1px solid var(--text-lime); + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition-duration: 0.2s; + } + + .add-source-btn:hover { + background-color: var(--text-lime); + color: var(--bg-primary); + transform: scale(1.025); + } + + .source-item { + margin-bottom: 15px; + } + + .source-item label { + margin-bottom: 6px; + font-size: 12px; + color: #888888; + } + + .source-input-group { + display: flex; + gap: 10px; + align-items: center; + } + + .source-input-group input { + flex: 1; + } + + .remove-source-btn { + background-color: var(--btn-secondary); + color: #888888; + border: none; + padding: 8px; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition-duration: 0.2s; + min-width: 40px; + } + + .remove-source-btn:hover { + color: #ef4444; + background-color: var(--btn-primary); + transform: scale(1.025); + } +</style> |
