Subir todos los cambios recientes a la rama principal
This commit is contained in:
@@ -29,12 +29,13 @@ const initialState: AppState = {
|
||||
// API helper function
|
||||
async function apiCall(endpoint: string, options: RequestInit = {}) {
|
||||
const authState = get(auth);
|
||||
|
||||
|
||||
const response = await fetch(`/api/v1${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${authState.token}`,
|
||||
'X-Tenant-ID': authState.tenantId, // Added tenant header
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
@@ -60,8 +61,10 @@ function createAppStore() {
|
||||
|
||||
try {
|
||||
const categories = await apiCall('/categories/');
|
||||
console.log('Loaded categories:', categories); // Debugging
|
||||
update(state => ({ ...state, categories, isLoading: false }));
|
||||
} catch (error) {
|
||||
console.error('Error loading categories:', error); // Debugging
|
||||
update(state => ({
|
||||
...state,
|
||||
isLoading: false,
|
||||
|
||||
@@ -107,6 +107,7 @@ function createAuthStore() {
|
||||
isLoading: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error); // Debugging the error
|
||||
update(state => ({ ...state, isLoading: false }));
|
||||
throw error;
|
||||
}
|
||||
|
||||
44
frontend-client/src/lib/stores/clientes.ts
Normal file
44
frontend-client/src/lib/stores/clientes.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const clients = writable([]);
|
||||
|
||||
export async function fetchClients() {
|
||||
const response = await fetch('/api/v1/clients');
|
||||
const data = await response.json();
|
||||
clients.set(data);
|
||||
}
|
||||
|
||||
export async function createClient(client) {
|
||||
const response = await fetch('/api/v1/clients', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(client),
|
||||
});
|
||||
if (response.ok) {
|
||||
fetchClients();
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateClient(clientId, client) {
|
||||
const response = await fetch(`/api/v1/clients/${clientId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(client),
|
||||
});
|
||||
if (response.ok) {
|
||||
fetchClients();
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteClient(clientId) {
|
||||
const response = await fetch(`/api/v1/clients/${clientId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (response.ok) {
|
||||
fetchClients();
|
||||
}
|
||||
}
|
||||
53
frontend-client/src/routes/clients/+page.svelte
Normal file
53
frontend-client/src/routes/clients/+page.svelte
Normal file
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { clients } from '$lib/stores/clients';
|
||||
import { toast } from '$lib/stores/toast';
|
||||
|
||||
let clientList = [];
|
||||
let isLoading = true;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
clientList = await clients.loadClients();
|
||||
} catch (error) {
|
||||
toast.error('Error al cargar los clientes');
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Clientes - ServiceManager</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Listado de Clientes</h1>
|
||||
|
||||
{#if isLoading}
|
||||
<p>Cargando clientes...</p>
|
||||
{:else if clientList.length === 0}
|
||||
<p>No hay clientes registrados.</p>
|
||||
{:else}
|
||||
<table class="table-auto w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-gray-300 px-4 py-2">Nombre</th>
|
||||
<th class="border border-gray-300 px-4 py-2">Email</th>
|
||||
<th class="border border-gray-300 px-4 py-2">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each clientList as client}
|
||||
<tr>
|
||||
<td class="border border-gray-300 px-4 py-2">{client.name}</td>
|
||||
<td class="border border-gray-300 px-4 py-2">{client.email}</td>
|
||||
<td class="border border-gray-300 px-4 py-2">
|
||||
<a href={`/clients/${client.id}/edit`} class="text-blue-500 hover:underline">Editar</a>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
83
frontend-client/src/routes/clients/[id]/edit/+page.svelte
Normal file
83
frontend-client/src/routes/clients/[id]/edit/+page.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { clients } from '$lib/stores/clients';
|
||||
import { toast } from '$lib/stores/toast';
|
||||
import { goto, page } from '$app/navigation';
|
||||
|
||||
let clientId = $page.params.id;
|
||||
let name = '';
|
||||
let email = '';
|
||||
let isSubmitting = false;
|
||||
let errors: Record<string, string> = {};
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const client = await clients.loadClient(clientId);
|
||||
name = client.name;
|
||||
email = client.email;
|
||||
} catch (error) {
|
||||
toast.error('Error al cargar el cliente');
|
||||
goto('/clients');
|
||||
}
|
||||
});
|
||||
|
||||
function validateForm() {
|
||||
errors = {};
|
||||
|
||||
if (!name.trim()) {
|
||||
errors.name = 'El nombre es requerido';
|
||||
}
|
||||
|
||||
if (!email.trim()) {
|
||||
errors.email = 'El email es requerido';
|
||||
}
|
||||
|
||||
return Object.keys(errors).length === 0;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!validateForm()) return;
|
||||
|
||||
isSubmitting = true;
|
||||
|
||||
try {
|
||||
await clients.updateClient(clientId, { name, email });
|
||||
toast.success('Cliente actualizado exitosamente');
|
||||
goto('/clients');
|
||||
} catch (error) {
|
||||
toast.error('Error al actualizar el cliente');
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Editar Cliente - ServiceManager</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Editar Cliente</h1>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">Nombre</label>
|
||||
<input id="name" type="text" bind:value={name} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm" />
|
||||
{#if errors.name}
|
||||
<p class="text-red-500 text-sm">{errors.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input id="email" type="email" bind:value={email} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm" />
|
||||
{#if errors.email}
|
||||
<p class="text-red-500 text-sm">{errors.email}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Guardando...' : 'Guardar Cambios'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
71
frontend-client/src/routes/clients/new/+page.svelte
Normal file
71
frontend-client/src/routes/clients/new/+page.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { clients } from '$lib/stores/clients';
|
||||
import { toast } from '$lib/stores/toast';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let name = '';
|
||||
let email = '';
|
||||
let isSubmitting = false;
|
||||
let errors: Record<string, string> = {};
|
||||
|
||||
function validateForm() {
|
||||
errors = {};
|
||||
|
||||
if (!name.trim()) {
|
||||
errors.name = 'El nombre es requerido';
|
||||
}
|
||||
|
||||
if (!email.trim()) {
|
||||
errors.email = 'El email es requerido';
|
||||
}
|
||||
|
||||
return Object.keys(errors).length === 0;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!validateForm()) return;
|
||||
|
||||
isSubmitting = true;
|
||||
|
||||
try {
|
||||
await clients.createClient({ name, email });
|
||||
toast.success('Cliente creado exitosamente');
|
||||
goto('/clients');
|
||||
} catch (error) {
|
||||
toast.error('Error al crear el cliente');
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Crear Cliente - ServiceManager</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container mx-auto py-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Crear Nuevo Cliente</h1>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">Nombre</label>
|
||||
<input id="name" type="text" bind:value={name} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm" />
|
||||
{#if errors.name}
|
||||
<p class="text-red-500 text-sm">{errors.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input id="email" type="email" bind:value={email} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm" />
|
||||
{#if errors.email}
|
||||
<p class="text-red-500 text-sm">{errors.email}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Creando...' : 'Crear Cliente'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user