Añadido el editar usuarios

This commit is contained in:
2025-12-02 20:26:31 -03:00
parent 94d967ca77
commit b8c8ff503b
6 changed files with 203 additions and 12 deletions

View File

@@ -15,19 +15,23 @@
import RecuperarContraseña from './admin/RecuperarContraseña.svelte';
import { Dialog } from './ui/dialog';
import DialogContent from './ui/dialog/dialog-content.svelte';
import ModificarUsuario from './admin/ModificarUsuario.svelte';
import { fade } from 'svelte/transition';
interface Props {
usuarios: UserResponseDto[];
}
let { usuarios }: Props = $props();
let { usuarios = $bindable() }: Props = $props();
let open = $state(false);
const openModificarUsuario = $state(false);
let openModificarUsuario = $state(false);
//si ponia contraseña en español quedaba muy largo el nombre
let usuarioCambioPass: UserResponseDto | null = $state(null);
let usuarioModificar: UserResponseDto | null = $state(null);
$effect(() => {
if (!open) {
usuarioCambioPass = null;
@@ -38,9 +42,13 @@
open = true;
usuarioCambioPass = usuario;
}
function handleModificar(usuario: UserResponseDto) {
openModificarUsuario = true;
usuarioModificar = usuario;
}
</script>
<!-- {$inspect(usuarios)} -->
<Table>
<TableHeader>
<TableRow>
@@ -73,7 +81,7 @@
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button><UserPen /></Button>
<Button onclick={() => handleModificar(usuario)}><UserPen /></Button>
</TooltipTrigger>
<TooltipContent>
<p>Modificar Usuario</p>
@@ -86,3 +94,4 @@
</TableBody>
</Table>
<RecuperarContraseña bind:open usuario={usuarioCambioPass} />
<ModificarUsuario bind:open={openModificarUsuario} bind:usuario={usuarioModificar} />

View File

@@ -0,0 +1,101 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { Dialog, DialogTitle, DialogContent, DialogHeader } from '../ui/dialog';
import InputGroup from '../ui/input-group/input-group.svelte';
import InputGroupInput from '../ui/input-group/input-group-input.svelte';
import InputGroupAddon from '../ui/input-group/input-group-addon.svelte';
import Button from '../ui/button/button.svelte';
import type { UserResponseDto } from '../../../types';
import Checkbox from '../ui/checkbox/checkbox.svelte';
import Label from '../ui/label/label.svelte';
import Spinner from '../ui/spinner/spinner.svelte';
import { updateUsuario } from '@/hooks/updateUsuario';
interface Prop {
open: boolean;
usuario: UserResponseDto | null;
}
let { open = $bindable(), usuario = $bindable() }: Prop = $props();
let imagen = $state(!!usuario?.profileImageUrl);
let fallback = usuario?.displayName;
let cargando = $state(false);
let error = $state('');
async function onsubmit(e: SubmitEvent) {
e.preventDefault();
cargando = true;
let ret: { displayName: string } | string = await updateUsuario({
id: usuario?.id || '',
bio: usuario?.bio || '',
displayName: usuario?.displayName || '',
oldImageUrl: usuario?.profileImageUrl || '',
profileImage: imagen
});
if (typeof ret === 'string') {
error = ret;
} else {
usuario!.displayName = ret.displayName;
open = false;
}
cargando = false;
}
</script>
<div transition:fade>
<Dialog
{open}
onOpenChange={() => {
open = !open;
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Modificar Usuario</DialogTitle>
</DialogHeader>
<form {onsubmit}>
<div class="flex flex-col gap-3">
<InputGroup>
<InputGroupInput disabled={cargando} bind:value={usuario!.displayName} />
<InputGroupAddon>Nombre</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput disabled={cargando} bind:value={usuario!.bio} />
<InputGroupAddon>Bio</InputGroupAddon>
</InputGroup>
<div class="ms-1 flex gap-2">
<Checkbox disabled={usuario?.profileImageUrl == null || cargando} bind:checked={imagen}
></Checkbox>
<Label>
{#if usuario?.profileImageUrl == null}
No tiene imagen de perfil
{:else if imagen}
Borrar imagen
{:else}
Mantener imagen
{/if}
</Label>
</div>
<hr class="my-2" />
<div class="flex justify-between">
<Button type="submit" disabled={cargando}>
{#if cargando}
<Spinner /> Cargando...
{:else}
Aceptar
{/if}
</Button>
<Button variant="secondary" disabled={cargando}>Cerrar</Button>
</div>
</div>
</form>
</DialogContent>
</Dialog>
</div>
<div transition:fade>
<Dialog open={error != ''} onOpenChange={() => (error = '')}>
<DialogContent>
{error}
</DialogContent>
</Dialog>
</div>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui";
import CheckIcon from "@lucide/svelte/icons/check";
import MinusIcon from "@lucide/svelte/icons/minus";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>
<CheckboxPrimitive.Root
bind:ref
data-slot="checkbox"
class={cn(
"border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:checked
bind:indeterminate
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<div data-slot="checkbox-indicator" class="text-current transition-none">
{#if checked}
<CheckIcon class="size-3.5" />
{:else if indeterminate}
<MinusIcon class="size-3.5" />
{/if}
</div>
{/snippet}
</CheckboxPrimitive.Root>

View File

@@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox,
};

View File

@@ -0,0 +1,44 @@
import { apiBase } from "@/stores/url"
import { sesionStore } from "@/stores/usuario"
import { get } from "svelte/store"
export interface AdminUpdateUsuario {
id:string,
displayName: string,
bio: string,
profileImage:boolean,
oldImageUrl:string
}
export async function updateUsuario(usuario: AdminUpdateUsuario) {
const formData = new FormData();
formData.append('displayName', usuario.displayName);
formData.append('bio', usuario.bio);
if (usuario.profileImage) {
formData.append('profileImageUrl', usuario.oldImageUrl);
}
try {
const req = await fetch(get(apiBase) + "/api/users/"+usuario.id, {
method: "PUT",
headers: {
Authorization: `Bearer ${get(sesionStore)?.accessToken}`
},
body: formData,
});
if (req.status === 204) {
let ret = {
// bio: usuario.bio,
displayName: usuario.displayName,
// oldImageUrl: usuario.oldImageUrl,
}
return ret;
}
const dataa = await req.json();
return dataa.message;
} catch {
return "No se pudo alcanzar el servidor"
}
}

View File

@@ -8,6 +8,7 @@
import CardHeader from '@/components/ui/card/card-header.svelte';
let cargando = $state(true);
let usuarios = $state(page.data.usuarios);
</script>
<div class="flex min-h-fit w-full items-center justify-center p-6 md:p-10">
@@ -22,15 +23,9 @@
</CardHeader>
<CardContent>
{#if page.data.usuarios.length === 0}
{#if page.data.error}
<CardDescription class="flex items-center justify-center space-x-2 text-destructive">
<span class="text-sm">Error al cargar usuarios.</span>
</CardDescription>
{:else}
<CardDescription>No hay posts que mostar</CardDescription>
{/if}
<CardDescription>No hay posts que mostar</CardDescription>
{:else}
<TablaUsuarios usuarios={page.data.usuarios}></TablaUsuarios>
<TablaUsuarios bind:usuarios></TablaUsuarios>
{/if}
</CardContent>
</Card>