ADD new frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 10:41:04 +02:00
parent 9c247a5f99
commit 0fd273cfce
492 changed files with 114979 additions and 1632 deletions

View File

@@ -0,0 +1,229 @@
import React, { useState } from 'react';
import { Plus, Search, Filter, Download, AlertTriangle } from 'lucide-react';
import { Button, Input, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { InventoryTable, InventoryForm, LowStockAlert } from '../../../../components/domain/inventory';
const InventoryPage: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [showForm, setShowForm] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const [filterCategory, setFilterCategory] = useState('all');
const [filterStatus, setFilterStatus] = useState('all');
const mockInventoryItems = [
{
id: '1',
name: 'Harina de Trigo',
category: 'Harinas',
currentStock: 45,
minStock: 20,
maxStock: 100,
unit: 'kg',
cost: 1.20,
supplier: 'Molinos del Sur',
lastRestocked: '2024-01-20',
expirationDate: '2024-06-30',
status: 'normal',
},
{
id: '2',
name: 'Levadura Fresca',
category: 'Levaduras',
currentStock: 8,
minStock: 10,
maxStock: 25,
unit: 'kg',
cost: 8.50,
supplier: 'Levaduras SA',
lastRestocked: '2024-01-25',
expirationDate: '2024-02-15',
status: 'low',
},
{
id: '3',
name: 'Mantequilla',
category: 'Lácteos',
currentStock: 15,
minStock: 5,
maxStock: 30,
unit: 'kg',
cost: 5.80,
supplier: 'Lácteos Frescos',
lastRestocked: '2024-01-24',
expirationDate: '2024-02-10',
status: 'normal',
},
];
const lowStockItems = mockInventoryItems.filter(item => item.status === 'low');
const stats = {
totalItems: mockInventoryItems.length,
lowStockItems: lowStockItems.length,
totalValue: mockInventoryItems.reduce((sum, item) => sum + (item.currentStock * item.cost), 0),
needsReorder: lowStockItems.length,
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Gestión de Inventario"
description="Controla el stock de ingredientes y materias primas"
action={
<Button onClick={() => setShowForm(true)}>
<Plus className="w-4 h-4 mr-2" />
Nuevo Artículo
</Button>
}
/>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Total Artículos</p>
<p className="text-3xl font-bold text-[var(--text-primary)]">{stats.totalItems}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-info)]/10 rounded-full flex items-center justify-center">
<svg className="h-6 w-6 text-[var(--color-info)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4-8-4m16 0v10l-8 4-8-4V7" />
</svg>
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Stock Bajo</p>
<p className="text-3xl font-bold text-[var(--color-error)]">{stats.lowStockItems}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-error)]/10 rounded-full flex items-center justify-center">
<AlertTriangle className="h-6 w-6 text-[var(--color-error)]" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Valor Total</p>
<p className="text-3xl font-bold text-[var(--color-success)]">{stats.totalValue.toFixed(2)}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-success)]/10 rounded-full flex items-center justify-center">
<svg className="h-6 w-6 text-[var(--color-success)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1" />
</svg>
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Necesita Reorden</p>
<p className="text-3xl font-bold text-[var(--color-primary)]">{stats.needsReorder}</p>
</div>
<div className="h-12 w-12 bg-[var(--color-primary)]/10 rounded-full flex items-center justify-center">
<svg className="h-6 w-6 text-[var(--color-primary)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</div>
</div>
</Card>
</div>
{/* Low Stock Alert */}
{lowStockItems.length > 0 && (
<LowStockAlert items={lowStockItems} />
)}
{/* Filters and Search */}
<Card className="p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] h-4 w-4" />
<Input
placeholder="Buscar artículos..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2">
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="all">Todas las categorías</option>
<option value="Harinas">Harinas</option>
<option value="Levaduras">Levaduras</option>
<option value="Lácteos">Lácteos</option>
<option value="Grasas">Grasas</option>
<option value="Azúcares">Azúcares</option>
<option value="Especias">Especias</option>
</select>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-3 py-2 border border-[var(--border-secondary)] rounded-md"
>
<option value="all">Todos los estados</option>
<option value="normal">Stock normal</option>
<option value="low">Stock bajo</option>
<option value="out">Sin stock</option>
<option value="expired">Caducado</option>
</select>
<Button variant="outline">
<Filter className="w-4 h-4 mr-2" />
Más filtros
</Button>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Exportar
</Button>
</div>
</div>
</Card>
{/* Inventory Table */}
<Card>
<InventoryTable
data={mockInventoryItems}
onEdit={(item) => {
setSelectedItem(item);
setShowForm(true);
}}
/>
</Card>
{/* Inventory Form Modal */}
{showForm && (
<InventoryForm
item={selectedItem}
onClose={() => {
setShowForm(false);
setSelectedItem(null);
}}
onSave={(item) => {
// Handle save logic
console.log('Saving item:', item);
setShowForm(false);
setSelectedItem(null);
}}
/>
)}
</div>
);
};
export default InventoryPage;

View File

@@ -0,0 +1,232 @@
import React, { useState } from 'react';
import { Plus, Search, Filter, Download, AlertTriangle } from 'lucide-react';
import { Button, Input, Card, Badge } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { InventoryTable, InventoryForm, LowStockAlert } from '../../../../components/domain/inventory';
const InventoryPage: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [showForm, setShowForm] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const [filterCategory, setFilterCategory] = useState('all');
const [filterStatus, setFilterStatus] = useState('all');
const mockInventoryItems = [
{
id: '1',
name: 'Harina de Trigo',
category: 'Harinas',
currentStock: 45,
minStock: 20,
maxStock: 100,
unit: 'kg',
cost: 1.20,
supplier: 'Molinos del Sur',
lastRestocked: '2024-01-20',
expirationDate: '2024-06-30',
status: 'normal',
},
{
id: '2',
name: 'Levadura Fresca',
category: 'Levaduras',
currentStock: 8,
minStock: 10,
maxStock: 25,
unit: 'kg',
cost: 8.50,
supplier: 'Levaduras SA',
lastRestocked: '2024-01-25',
expirationDate: '2024-02-15',
status: 'low',
},
{
id: '3',
name: 'Mantequilla',
category: 'Lácteos',
currentStock: 15,
minStock: 5,
maxStock: 30,
unit: 'kg',
cost: 5.80,
supplier: 'Lácteos Frescos',
lastRestocked: '2024-01-24',
expirationDate: '2024-02-10',
status: 'normal',
},
];
const lowStockItems = mockInventoryItems.filter(item => item.status === 'low');
const stats = {
totalItems: mockInventoryItems.length,
lowStockItems: lowStockItems.length,
totalValue: mockInventoryItems.reduce((sum, item) => sum + (item.currentStock * item.cost), 0),
needsReorder: lowStockItems.length,
};
return (
<div className="p-6 space-y-6">
<PageHeader
title="Gestión de Inventario"
description="Controla el stock de ingredientes y materias primas"
action={
<Button onClick={() => setShowForm(true)}>
<Plus className="w-4 h-4 mr-2" />
Nuevo Artículo
</Button>
}
/>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Artículos</p>
<p className="text-3xl font-bold text-gray-900">{stats.totalItems}</p>
</div>
<div className="h-12 w-12 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 7l-8-4-8 4m16 0l-8 4-8-4m16 0v10l-8 4-8-4V7" />
</svg>
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Stock Bajo</p>
<p className="text-3xl font-bold text-red-600">{stats.lowStockItems}</p>
</div>
<div className="h-12 w-12 bg-red-100 rounded-full flex items-center justify-center">
<AlertTriangle className="h-6 w-6 text-red-600" />
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Valor Total</p>
<p className="text-3xl font-bold text-green-600">€{stats.totalValue.toFixed(2)}</p>
</div>
<div className="h-12 w-12 bg-green-100 rounded-full flex items-center justify-center">
<svg className="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1" />
</svg>
</div>
</div>
</Card>
<Card className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Necesita Reorden</p>
<p className="text-3xl font-bold text-orange-600">{stats.needsReorder}</p>
</div>
<div className="h-12 w-12 bg-orange-100 rounded-full flex items-center justify-center">
<svg className="h-6 w-6 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</div>
</div>
</Card>
</div>
{/* Low Stock Alert */}
{lowStockItems.length > 0 && (
<LowStockAlert items={lowStockItems} />
)}
{/* Filters and Search */}
<Card className="p-6">
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="Buscar artículos..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2">
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="all">Todas las categorías</option>
<option value="Harinas">Harinas</option>
<option value="Levaduras">Levaduras</option>
<option value="Lácteos">Lácteos</option>
<option value="Grasas">Grasas</option>
<option value="Azúcares">Azúcares</option>
<option value="Especias">Especias</option>
</select>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md"
>
<option value="all">Todos los estados</option>
<option value="normal">Stock normal</option>
<option value="low">Stock bajo</option>
<option value="out">Sin stock</option>
<option value="expired">Caducado</option>
</select>
<Button variant="outline">
<Filter className="w-4 h-4 mr-2" />
Más filtros
</Button>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
Exportar
</Button>
</div>
</div>
</Card>
{/* Inventory Table */}
<Card>
<InventoryTable
items={mockInventoryItems}
searchTerm={searchTerm}
filterCategory={filterCategory}
filterStatus={filterStatus}
onEdit={(item) => {
setSelectedItem(item);
setShowForm(true);
}}
/>
</Card>
{/* Inventory Form Modal */}
{showForm && (
<InventoryForm
item={selectedItem}
onClose={() => {
setShowForm(false);
setSelectedItem(null);
}}
onSave={(item) => {
// Handle save logic
console.log('Saving item:', item);
setShowForm(false);
setSelectedItem(null);
}}
/>
)}
</div>
);
};
export default InventoryPage;

View File

@@ -0,0 +1 @@
export { default as InventoryPage } from './InventoryPage';