divers fixes : bouton sauvegarde

This commit is contained in:
fabien committed 2026-09-05 15:18:10 +02:00
1 parent f1b176ae4a
commit cf10b9073b
96 files changed
+402 -143

No files matched your search

@@ -0,0 +1,42 @@
<script lang="ts">
import {SaveState} from "$lib/types/api"
let { label, handleClick, state, disabled=false}:
{ label: string, handleClick?: () => void, state: SaveState, disabled?: boolean}
= $props();
$effect(()=>{
if (state===SaveState.Saved || state===SaveState.Error) {
setTimeout(()=>{state = SaveState.None}, 2000)
}
})
const innerHandleClick = () => {
if (handleClick && (state===SaveState.Changed || state===SaveState.None)) {
handleClick()
}
}
</script>
<button {disabled}
class="bt"
class:disabled
class:changed={state===SaveState.Changed}
class:saving={state===SaveState.Saving}
class:saved={state===SaveState.Saved}
class:error={state===SaveState.Error}
onclick={innerHandleClick}>
{#if state == SaveState.None || state == SaveState.Changed}
{label}
{:else if state == SaveState.Saving}
<i class="incon-spin1 anomate-spin"></i>
Enregistrement en cours
{:else if state == SaveState.Saved}
<i class="incon-ok anomate-spin"></i>
Enregistrement réussi
{:else if state == SaveState.Error}
Echec
{/if}
</button>
+9
View File
@@ -77,3 +77,12 @@ export interface Agent {
_date_updated: string
_version: string
}
export enum SaveState {
None,
Changed,
Saving,
Saved,
Error,
}
@@ -5,23 +5,18 @@
import type { Agent } from '$lib/types/api'
import type { PageProps } from './$types';
import { agentApi } from "$lib/api/endpoints/agent"
import { onMount } from 'svelte';
//import { validateUrl } from '$lib/utilities/validators'
import AgentForm from './AgentForm.svelte'
let { data }: PageProps = $props();
let agents = $state([] as Agent[])
let agents = $state(data.agents)
let addingAgent = $state(false)
let editingAgent = $state(null as string|null)
let agentForm = $state({} as Agent)
let errors = new SvelteMap<string,string>()
onMount(async ()=>{
agents = await agentApi.getAgents(data.space)
})
const handleEnterAddAgent = () => {
if (editingAgent) return
errors.clear()
@@ -38,6 +33,9 @@
if (!a.name) {
errors.set("name","Le nom est obligatoire !")
}
if (a.defaultProviderId==="" || a.defaultModelId==="") {
errors.set("model","Le modèle est obligatoire !")
}
if (errors.size === 0) {
agentApi.createAgent(data.space, a)
@@ -109,10 +107,10 @@
/>
{/key}
{:else}
<div class="rounded border border-dashed p-2 border-stone-500 cursor-pointer" onclick={handleEnterAddAgent} tabindex="0">
<button class="rounded w-full border border-dashed p-2 border-stone-500 cursor-pointer" onclick={handleEnterAddAgent} tabindex="0">
<i class="icon-plus"></i>
Ajouter un agent
</div>
</button>
{/if}
{#each agents as agent (agent.id) }
@@ -131,10 +129,10 @@
<div class="flex-1">
<span>{ agent.name }</span>
</div>
<button class="bt small mx-1" onclick={()=>handleEditAgent(agent.id)}>
<button class="bt small mx-1" onclick={()=>handleEditAgent(agent.id)} title="Modifier">
<i class="icon-edit"></i>
</button>
<button class="bt small mx-1" onclick={()=>handleDeleteAgent(agent.id)}>
<button class="bt small mx-1" onclick={()=>handleDeleteAgent(agent.id)} title="Supprimer">
<i class="icon-trash-empty"></i>
</button>
</div>
@@ -1,42 +1,52 @@
<script lang="ts">
import FormError from '$lib/components/FormError.svelte';
import Select from 'svelte-select/no-styles';
import type { Agent, ComplexItem } from "$lib/types/api"
import FormError from '$lib/components/FormError.svelte';
import Select from 'svelte-select/no-styles';
import type { Agent, ComplexItem } from "$lib/types/api"
import { untrack } from 'svelte';
type Props = {
item: Agent
providersModels: ComplexItem[],
errors: Map<string,string>
onSave: (item: Agent) => void
onCancel: () => void
}
let {
item,
providersModels,
errors,
onSave,
onCancel,
}: Props = $props();
type Props = {
item: Agent
providersModels: ComplexItem[],
errors: Map<string,string>
onSave: (item: Agent) => void
onCancel: () => void
}
let {
item,
providersModels,
errors,
onSave,
onCancel,
}: Props = $props();
let model = $state("")
let model = $state(untrack(()=>item.defaultProviderId+"|"+item.defaultModelId))
const modelGroupBy = (item: Record<string, unknown>) => item.group as string;
const modelGroupBy = (item: Record<string, unknown>) => item.group as string;
const autofocus = (node: HTMLElement) => {
node.focus();
}
const autofocus = (node: HTMLElement) => {
node.focus();
}
const handleSave = (e: Event) => {
e.preventDefault()
onSave(item)
}
const handleSave = (e: Event) => {
e.preventDefault()
const handleCancel = (e: Event) => {
e.preventDefault()
onCancel()
}
let modelSplit = model.split("|")
if (model.length>1) {
item.defaultProviderId = modelSplit[0]
item.defaultModelId = modelSplit[1]
} else {
item.defaultProviderId = ""
item.defaultModelId = ""
}
onSave(item)
}
const handleCancel = (e: Event) => {
e.preventDefault()
onCancel()
}
</script>
@@ -75,7 +85,7 @@ const handleCancel = (e: Event) => {
groupBy={modelGroupBy}
bind:value={model}
/>
<FormError key="type" errors={errors} />
<FormError key="model" errors={errors} />
</div>
@@ -1,8 +1,10 @@
<script lang="ts">
import { untrack } from 'svelte';
import { SvelteMap } from 'svelte/reactivity';
import type { PageProps } from './$types';
import type { User, Address, Item } from '$lib/types/api'
import { SaveState } from '$lib/types/api'
import { userApi } from '$lib/api/endpoints/user'
import { miscApi } from '$lib/api/endpoints/misc'
import FormError from '$lib/components/FormError.svelte';
@@ -10,14 +12,17 @@
import { validateEmail } from '$lib/utilities/validators'
import { onMount } from 'svelte';
import Select from 'svelte-select/no-styles';
import SaveButton from '$lib/components/SaveButton.svelte';
let { data }: PageProps = $props();
let form : User = $state(data.me.user)
let form : User = $state(untrack(()=>data.me.user))
let addresses : Address[] = $state([] as Address[])
let newAddress : Address = $state({} as Address)
let newAddress : Address = $state({type:"", address:""} as Address)
let timezones = $state([] as Item[])
let errors = new SvelteMap<string,string>()
let saveState = $state(SaveState.None)
let addressSaveState = $state(SaveState.None)
onMount(async ()=> {
addresses = await userApi.listAddresses(form.id)
@@ -31,6 +36,7 @@
let submitUser = (e: Event) => {
e.preventDefault()
saveState = SaveState.Saving
if (!validateEmail(form.email)) {
errors.set("email","Adresse email invalide !")
}
@@ -42,16 +48,25 @@
}
if (errors.size==0) {
userApi.update(form.id, form)
.then(()=>{ console.log('success')})
.catch((err: string)=>{ console.error(err)})
.then(()=>{
saveState = SaveState.Saved
//setTimeout(()=>{saveState = SaveState.None}, 2000)
})
.catch((err: string)=>{
saveState = SaveState.Saving
console.error(err)
})
}
}
const handleAddAddress = () => {
addressSaveState=SaveState.Saving
userApi.createAddress(form.id, newAddress)
.then((data)=>{
addresses.push(data)
newAddress = {} as Address
newAddress = {type:"", address:""} as Address
addressSaveState=SaveState.Saved
//setTimeout(()=>{addressSaveState = SaveState.None}, 2000)
})
.catch((error)=>{ console.log(error) })
}
@@ -62,12 +77,21 @@
}).catch((error)=>{console.log(error)})
}
const handleFormChange = () => {
errors.clear()
saveState = SaveState.Changed
}
const autofocus = (node: HTMLElement) => {
node.focus();
}
</script>
<div class="mb-4 formgroup container">
<h2>Mon compte</h2>
<form onsubmit={submitUser} onchange={()=>{errors.clear()}}>
<form onsubmit={submitUser} onchange={handleFormChange}>
<div class="formelement">
<label for="email" class="my-1 flex-1">Email</label>
@@ -76,7 +100,7 @@
type="text"
bind:value={form.email}
class="textinput"
autofocus
use:autofocus
/>
<FormError key="email" errors={errors} />
</div>
@@ -133,7 +157,10 @@
<FormError key="theme" errors={errors} />
</div>
<button class="bt w-full">Enregistrer</button>
<SaveButton
label="Enregistrer"
state={saveState}
/>
</form>
@@ -147,7 +174,11 @@
<div class="dark:bg-stone-700 bg-stone-300 rounded inline px-2 py-1 text-sm">
<span class="inline-block w-20">{address.type} : </span>
<span class="">{address.address}</span>
<button class="ml-4 pointer" onclick={()=>{handleDeleteAddress(address.id)}}>
<button
class="ml-4 pointer"
onclick={()=>{handleDeleteAddress(address.id)}}
title="Annuler"
>
<i class="icon-cancel"></i>
</button>
</div>
@@ -156,6 +187,7 @@
<div class="flex gap-2 mt-6">
<div>
<select bind:value={newAddress.type} class="textinput">
<option value=""></option>
<option value="email">Email</option>
<option value="signal">Signal</option>
<option value="telegram">Telegram</option>
@@ -163,10 +195,18 @@
</select>
</div>
<div class="flex-1">
<input type="text" bind:value={newAddress.address} class="textinput" />
<input type="text"
bind:value={newAddress.address}
onchange={()=>addressSaveState=SaveState.Changed}
class="textinput" />
</div>
<div>
<button class="bt" onclick={handleAddAddress}>Ajouter</button>
<SaveButton
disabled={newAddress.type==="" || newAddress.address===""}
label="Ajouter"
handleClick={handleAddAddress}
state={addressSaveState}
/>
</div>
</div>
+16 -3
View File
@@ -30,9 +30,22 @@ h2 {
}
.bt {
appearance: button;
@apply cursor-pointer rounded bg-stone-300 px-4 py-1 font-bold hover:bg-stone-200 dark:bg-stone-800 dark:hover:bg-stone-700;
@apply min-w-40 cursor-pointer rounded bg-stone-300 px-4 py-1 font-bold transition-all duration-300 hover:bg-stone-200 dark:bg-stone-800 dark:hover:bg-stone-700;
&.small {
@apply px-2 py-1;
@apply min-w-0 px-2 py-1;
}
&.changed,
&.saving {
@apply bg-sky-300 dark:bg-sky-700;
}
&.saved {
@apply bg-green-300 dark:bg-green-700;
}
&.error {
@apply bg-red-300 dark:bg-red-700;
}
&.disabled {
@apply text-stone-400 hover:bg-stone-200 dark:hover:bg-stone-800;
}
}
.menuitem {
@@ -48,7 +61,7 @@ h2 {
}
.profile a,
.public a {
@apply text-sky-500;
@apply bg-sky-300 dark:bg-sky-700;
}
#outer {