Fix frontend 1

This commit is contained in:
Urtzi Alfaro
2025-08-28 18:07:16 +02:00
parent 9ea6794923
commit 68bb5a6449
11 changed files with 427 additions and 400 deletions

View File

@@ -286,7 +286,7 @@ const QuickActions: React.FC<QuickActionsProps> = ({
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
sizeClasses[size],
{
'bg-white hover:bg-[var(--bg-secondary)]': !action.backgroundGradient,
'bg-[var(--bg-primary)] hover:bg-[var(--bg-secondary)]': !action.backgroundGradient,
'bg-gradient-to-br text-white hover:opacity-90': action.backgroundGradient,
'opacity-50 cursor-not-allowed hover:transform-none hover:shadow-none': isDisabled,
}

View File

@@ -499,7 +499,7 @@ export const InventoryForm: React.FC<InventoryFormProps> = ({
type="number"
step="0.01"
min="0"
value={formData.low_stock_threshold.toString()}
value={formData.low_stock_threshold?.toString() || ''}
onChange={(e) => handleInputChange('low_stock_threshold', parseFloat(e.target.value) || 0)}
error={errors.low_stock_threshold}
placeholder="10"
@@ -510,7 +510,7 @@ export const InventoryForm: React.FC<InventoryFormProps> = ({
type="number"
step="0.01"
min="0"
value={formData.reorder_point.toString()}
value={formData.reorder_point?.toString() || ''}
onChange={(e) => handleInputChange('reorder_point', parseFloat(e.target.value) || 0)}
error={errors.reorder_point}
placeholder="20"
@@ -521,7 +521,7 @@ export const InventoryForm: React.FC<InventoryFormProps> = ({
type="number"
step="0.01"
min="0"
value={formData.reorder_quantity.toString()}
value={formData.reorder_quantity?.toString() || ''}
onChange={(e) => handleInputChange('reorder_quantity', parseFloat(e.target.value) || 0)}
error={errors.reorder_quantity}
placeholder="50"

View File

@@ -205,6 +205,7 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
isOpen={isSidebarOpen}
isCollapsed={isSidebarCollapsed}
onClose={() => setIsSidebarOpen(false)}
onToggleCollapse={toggleSidebar}
className="z-[var(--z-fixed)]"
/>
@@ -223,15 +224,13 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
<main
className={clsx(
'flex-1 flex flex-col transition-all duration-300 ease-in-out',
// Adjust margins based on sidebar state
shouldShowSidebar && isAuthenticated && {
'lg:ml-[var(--sidebar-width)]': !isSidebarCollapsed,
'lg:ml-16': isSidebarCollapsed,
},
// Add header offset
shouldShowHeader && 'pt-[var(--header-height)]',
// Adjust margins based on sidebar state
shouldShowSidebar && isAuthenticated && !isSidebarCollapsed && 'lg:ml-[var(--sidebar-width)]',
shouldShowSidebar && isAuthenticated && isSidebarCollapsed && 'lg:ml-[var(--sidebar-collapsed-width)]',
// Add padding to content
padded && 'p-4 lg:p-6'
padded && 'px-4 lg:px-6 pb-4 lg:pb-6'
)}
role="main"
aria-label="Contenido principal"
@@ -247,10 +246,8 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
showPrivacyLinks={true}
className={clsx(
'transition-all duration-300 ease-in-out',
shouldShowSidebar && isAuthenticated && {
'lg:ml-[var(--sidebar-width)]': !isSidebarCollapsed,
'lg:ml-16': isSidebarCollapsed,
}
shouldShowSidebar && isAuthenticated && !isSidebarCollapsed && 'lg:ml-[var(--sidebar-width)]',
shouldShowSidebar && isAuthenticated && isSidebarCollapsed && 'lg:ml-[var(--sidebar-collapsed-width)]'
)}
/>
)}

View File

@@ -40,6 +40,10 @@ export interface SidebarProps {
* Callback when sidebar is closed (mobile)
*/
onClose?: () => void;
/**
* Callback when sidebar collapse state is toggled (desktop)
*/
onToggleCollapse?: () => void;
/**
* Custom navigation items
*/
@@ -110,6 +114,7 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
isOpen = false,
isCollapsed = false,
onClose,
onToggleCollapse,
customItems,
showCollapseButton = true,
showFooter = true,
@@ -340,12 +345,12 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
disabled={item.disabled}
data-path={item.path}
className={clsx(
'w-full p-3 rounded-lg transition-all duration-200',
'w-full rounded-lg transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20',
isActive && 'bg-[var(--color-primary)]/10 border-l-2 border-[var(--color-primary)]',
!isActive && 'hover:bg-[var(--bg-secondary)]',
item.disabled && 'opacity-50 cursor-not-allowed',
isCollapsed && !hasChildren && 'flex justify-center p-3'
isCollapsed && !hasChildren ? 'flex justify-center items-center p-2 mx-1' : 'p-3'
)}
aria-expanded={hasChildren ? isExpanded : undefined}
aria-current={isActive ? 'page' : undefined}
@@ -388,29 +393,29 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
'bg-[var(--bg-primary)] border-r border-[var(--border-primary)]',
'transition-all duration-300 ease-in-out z-[var(--z-fixed)]',
'hidden lg:flex lg:flex-col',
isCollapsed ? 'w-16' : 'w-[var(--sidebar-width)]',
isCollapsed ? 'w-[var(--sidebar-collapsed-width)]' : 'w-[var(--sidebar-width)]',
className
)}
aria-label="Navegación principal"
>
{/* Navigation */}
<nav className="flex-1 p-4 overflow-y-auto">
<ul className="space-y-2">
<nav className={clsx('flex-1 overflow-y-auto', isCollapsed ? 'px-1 py-4' : 'p-4')}>
<ul className={clsx(isCollapsed ? 'space-y-1' : 'space-y-2')}>
{visibleItems.map(item => renderItem(item))}
</ul>
</nav>
{/* Collapse button */}
{showCollapseButton && (
<div className="p-4 border-t border-[var(--border-primary)]">
<div className={clsx('border-t border-[var(--border-primary)]', isCollapsed ? 'p-2' : 'p-4')}>
<Button
variant="ghost"
size="sm"
onClick={() => {
// This should be handled by parent component
console.log('Toggle collapse');
}}
className="w-full flex items-center justify-center"
onClick={onToggleCollapse}
className={clsx(
'w-full flex items-center justify-center',
isCollapsed ? 'p-2' : 'px-4 py-2'
)}
aria-label={isCollapsed ? 'Expandir sidebar' : 'Contraer sidebar'}
>
{isCollapsed ? (

View File

@@ -229,14 +229,14 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
};
const variantClasses = {
default: 'border-collapse border border-table-border',
default: 'border-collapse',
striped: 'border-collapse',
bordered: 'border-collapse border border-table-border',
bordered: 'border-collapse border border-[var(--border-primary)]',
borderless: 'border-collapse',
};
const tableClasses = clsx(
'w-full bg-table-bg',
'w-full bg-[var(--bg-primary)]',
sizeClasses[size],
variantClasses[variant],
{
@@ -285,10 +285,10 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
const hasExpansion = expandable;
return (
<thead className={clsx('bg-table-header-bg', { 'sticky top-0 z-10': sticky })}>
<thead className={clsx('bg-[var(--bg-secondary)]', { 'sticky top-0 z-10': sticky })}>
<tr>
{hasSelection && (
<th className="px-4 py-3 text-left font-medium text-text-primary border-b border-table-border w-12">
<th className="px-4 py-3 text-left font-medium text-[var(--text-primary)] border-b border-[var(--border-primary)] w-12">
{rowSelection.type !== 'radio' && (
<input
type="checkbox"
@@ -302,7 +302,7 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
</th>
)}
{hasExpansion && (
<th className="px-4 py-3 text-left font-medium text-text-primary border-b border-table-border w-12"></th>
<th className="px-4 py-3 text-left font-medium text-[var(--text-primary)] border-b border-[var(--border-primary)] w-12"></th>
)}
{columns.map((column) => {
const isSorted = sortState.field === (column.sortKey || column.key);
@@ -311,12 +311,12 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
<th
key={column.key}
className={clsx(
'px-4 py-3 font-medium text-text-primary border-b border-table-border',
'px-4 py-3 font-medium text-[var(--text-primary)] border-b border-[var(--border-primary)]',
{
'text-left': column.align === 'left' || !column.align,
'text-center': column.align === 'center',
'text-right': column.align === 'right',
'cursor-pointer hover:bg-table-row-hover': column.sortable,
'cursor-pointer hover:bg-[var(--bg-secondary)] transition-colors duration-150': column.sortable,
},
column.headerClassName
)}
@@ -372,12 +372,13 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
<tr
{...rowProps}
className={clsx(
'transition-colors duration-150',
'group transition-colors duration-200 ease-in-out',
{
'bg-table-row-hover': hover && !isSelected,
'bg-table-row-selected': isSelected,
'odd:bg-bg-secondary': variant === 'striped' && !isSelected,
'border-b border-table-border': variant !== 'borderless',
'hover:bg-[var(--bg-secondary)] hover:shadow-sm': hover && !isSelected,
'bg-[var(--color-primary)]/10': isSelected,
'odd:bg-[var(--bg-tertiary)]/30': variant === 'striped' && !isSelected && !hover,
'odd:hover:bg-[var(--bg-secondary)]': variant === 'striped' && hover && !isSelected,
'border-b border-[var(--border-primary)]': variant !== 'borderless',
'cursor-pointer': expandable?.expandRowByClick,
},
rowProps.className
@@ -385,7 +386,7 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
onClick={expandable?.expandRowByClick ? () => handleExpand(record, !isExpanded) : rowProps.onClick}
>
{rowSelection && (
<td className="px-4 py-3 border-b border-table-border">
<td className="px-4 py-3 border-b border-[var(--border-primary)]">
<input
type={rowSelection.type || 'checkbox'}
className="rounded border-input-border focus:ring-color-primary"
@@ -398,7 +399,7 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
</td>
)}
{expandable && (
<td className="px-4 py-3 border-b border-table-border">
<td className="px-4 py-3 border-b border-[var(--border-primary)]">
{canExpand && (
<button
type="button"
@@ -431,11 +432,12 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
<td
key={column.key}
className={clsx(
'px-4 py-3 border-b border-table-border',
'px-4 py-3 border-b border-[var(--border-primary)] transition-colors duration-200 ease-in-out',
{
'text-left': column.align === 'left' || !column.align,
'text-center': column.align === 'center',
'text-right': column.align === 'right',
'group-hover:text-[var(--text-primary)]': true,
},
column.className
)}
@@ -450,7 +452,7 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
<tr>
<td
colSpan={columns.length + (rowSelection ? 1 : 0) + (expandable ? 1 : 0)}
className="px-4 py-0 border-b border-table-border bg-bg-secondary"
className="px-4 py-0 border-b border-[var(--border-primary)] bg-[var(--bg-secondary)]"
>
<div className="py-4" style={{ paddingLeft: expandable.indentSize || 24 }}>
{expandable.expandedRowRender(record, index)}
@@ -462,7 +464,7 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
);
})}
{summary && (
<tr className="bg-table-header-bg border-t-2 border-table-border font-medium">
<tr className="bg-[var(--bg-secondary)] border-t-2 border-[var(--border-primary)] font-medium">
<td colSpan={columns.length + (rowSelection ? 1 : 0) + (expandable ? 1 : 0)}>
{summary(data)}
</td>
@@ -472,8 +474,8 @@ const Table = forwardRef<HTMLTableElement, TableProps>(({
);
return (
<div className="space-y-4">
<div className={containerClasses}>
<div className="space-y-4 bg-[var(--bg-primary)]">
<div className={clsx(containerClasses, 'bg-[var(--bg-primary)]')}>
<table
ref={ref}
className={tableClasses}

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Calendar, TrendingUp, AlertTriangle, BarChart3, Download, Settings } from 'lucide-react';
import { Button, Card, Badge, Select } from '../../../../components/ui';
import { Button, Card, Badge, Select, Table } from '../../../../components/ui';
import type { TableColumn } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { DemandChart, ForecastTable, SeasonalityIndicator, AlertsPanel } from '../../../../components/domain/forecasting';
@@ -125,6 +126,57 @@ const ForecastingPage: React.FC = () => {
return <Badge variant={config?.color as any}>{config?.text}</Badge>;
};
const forecastColumns: TableColumn[] = [
{
key: 'product',
title: 'Producto',
dataIndex: 'product',
},
{
key: 'currentStock',
title: 'Stock Actual',
dataIndex: 'currentStock',
},
{
key: 'forecastDemand',
title: 'Demanda Prevista',
dataIndex: 'forecastDemand',
render: (value) => (
<span className="font-medium text-[var(--color-info)]">{value}</span>
),
},
{
key: 'recommendedProduction',
title: 'Producción Recomendada',
dataIndex: 'recommendedProduction',
render: (value) => (
<span className="font-medium text-[var(--color-success)]">{value}</span>
),
},
{
key: 'confidence',
title: 'Confianza',
dataIndex: 'confidence',
render: (value) => `${value}%`,
},
{
key: 'trend',
title: 'Tendencia',
dataIndex: 'trend',
render: (value) => (
<div className="flex items-center">
{getTrendIcon(value)}
</div>
),
},
{
key: 'stockoutRisk',
title: 'Riesgo Agotamiento',
dataIndex: 'stockoutRisk',
render: (value) => getRiskBadge(value),
},
];
return (
<div className="p-6 space-y-6">
<PageHeader
@@ -315,68 +367,16 @@ const ForecastingPage: React.FC = () => {
</div>
{/* Detailed Forecasts Table */}
<Card>
<div className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Predicciones Detalladas</h3>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-[var(--bg-secondary)]">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Producto
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Stock Actual
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Demanda Prevista
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Producción Recomendada
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Confianza
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Tendencia
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Riesgo Agotamiento
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{mockForecasts.map((forecast) => (
<tr key={forecast.id} className="hover:bg-[var(--bg-secondary)]">
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-[var(--text-primary)]">{forecast.product}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{forecast.currentStock}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-[var(--color-info)]">
{forecast.forecastDemand}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-[var(--color-success)]">
{forecast.recommendedProduction}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{forecast.confidence}%
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
{getTrendIcon(forecast.trend)}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getRiskBadge(forecast.stockoutRisk)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<Card className="p-6">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-4">Predicciones Detalladas</h3>
<Table
columns={forecastColumns}
data={mockForecasts}
rowKey="id"
hover={true}
variant="default"
size="md"
/>
</Card>
</div>
);

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Plus, Search, Filter, Download, Calendar, Clock, User, Package } from 'lucide-react';
import { Button, Input, Card, Badge } from '../../../../components/ui';
import { Button, Input, Card, Badge, Table } from '../../../../components/ui';
import type { TableColumn } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { OrdersTable, OrderForm } from '../../../../components/domain/sales';
@@ -115,6 +116,114 @@ const OrdersPage: React.FC = () => {
return <Badge variant={config?.color as any}>{config?.text || status}</Badge>;
};
const columns: TableColumn[] = [
{
key: 'id',
title: 'Pedido',
dataIndex: 'id',
render: (value, record: any) => (
<div>
<div className="text-sm font-medium text-[var(--text-primary)]">{value}</div>
<div className="text-xs text-[var(--text-tertiary)]">{record.deliveryMethod === 'delivery' ? 'Entrega' : 'Recogida'}</div>
</div>
),
},
{
key: 'customer',
title: 'Cliente',
render: (_, record: any) => (
<div className="flex items-center">
<User className="h-4 w-4 text-[var(--text-tertiary)] mr-2" />
<div>
<div className="text-sm font-medium text-[var(--text-primary)]">{record.customerName}</div>
<div className="text-xs text-[var(--text-tertiary)]">{record.customerEmail}</div>
</div>
</div>
),
},
{
key: 'status',
title: 'Estado',
dataIndex: 'status',
render: (value) => getStatusBadge(value),
},
{
key: 'priority',
title: 'Prioridad',
dataIndex: 'priority',
render: (value) => getPriorityBadge(value),
},
{
key: 'orderDate',
title: 'Fecha Pedido',
dataIndex: 'orderDate',
render: (value) => (
<div>
<div className="text-sm text-[var(--text-primary)]">{new Date(value).toLocaleDateString('es-ES')}</div>
<div className="text-xs text-[var(--text-tertiary)]">
{new Date(value).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })}
</div>
</div>
),
},
{
key: 'deliveryDate',
title: 'Entrega',
dataIndex: 'deliveryDate',
render: (value) => (
<div>
<div className="text-sm text-[var(--text-primary)]">{new Date(value).toLocaleDateString('es-ES')}</div>
<div className="text-xs text-[var(--text-tertiary)]">
{new Date(value).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })}
</div>
</div>
),
},
{
key: 'total',
title: 'Total',
dataIndex: 'total',
render: (value, record: any) => (
<div>
<div className="text-sm font-medium text-[var(--text-primary)]">{value.toFixed(2)}</div>
<div className="text-xs text-[var(--text-tertiary)]">{record.items.length} artículos</div>
</div>
),
},
{
key: 'payment',
title: 'Pago',
render: (_, record: any) => (
<div>
{getPaymentStatusBadge(record.paymentStatus)}
<div className="text-xs text-[var(--text-tertiary)] capitalize">{record.paymentMethod}</div>
</div>
),
},
{
key: 'actions',
title: 'Acciones',
align: 'right' as const,
render: (_, record: any) => (
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedOrder(record);
setShowForm(true);
}}
>
Ver
</Button>
<Button variant="outline" size="sm">
Editar
</Button>
</div>
),
},
];
const filteredOrders = mockOrders.filter(order => {
const matchesSearch = order.customerName.toLowerCase().includes(searchTerm.toLowerCase()) ||
order.id.toLowerCase().includes(searchTerm.toLowerCase()) ||
@@ -276,110 +385,15 @@ const OrdersPage: React.FC = () => {
</Card>
{/* Orders Table */}
<Card>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-[var(--bg-secondary)]">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Pedido
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Cliente
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Estado
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Prioridad
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Fecha Pedido
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Entrega
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Total
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Pago
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Acciones
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredOrders.map((order) => (
<tr key={order.id} className="hover:bg-[var(--bg-secondary)]">
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-[var(--text-primary)]">{order.id}</div>
<div className="text-xs text-[var(--text-tertiary)]">{order.deliveryMethod === 'delivery' ? 'Entrega' : 'Recogida'}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<User className="h-4 w-4 text-[var(--text-tertiary)] mr-2" />
<div>
<div className="text-sm font-medium text-[var(--text-primary)]">{order.customerName}</div>
<div className="text-xs text-[var(--text-tertiary)]">{order.customerEmail}</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getStatusBadge(order.status)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getPriorityBadge(order.priority)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{new Date(order.orderDate).toLocaleDateString('es-ES')}
<div className="text-xs text-[var(--text-tertiary)]">
{new Date(order.orderDate).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
})}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{new Date(order.deliveryDate).toLocaleDateString('es-ES')}
<div className="text-xs text-[var(--text-tertiary)]">
{new Date(order.deliveryDate).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
})}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-[var(--text-primary)]">{order.total.toFixed(2)}</div>
<div className="text-xs text-[var(--text-tertiary)]">{order.items.length} artículos</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getPaymentStatusBadge(order.paymentStatus)}
<div className="text-xs text-[var(--text-tertiary)] capitalize">{order.paymentMethod}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => {
setSelectedOrder(order);
setShowForm(true);
}}
>
Ver
</Button>
<Button variant="outline" size="sm">
Editar
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<Card className="p-6">
<Table
columns={columns}
data={filteredOrders}
rowKey="id"
hover={true}
variant="default"
size="md"
/>
</Card>
{/* Order Form Modal */}

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Plus, Search, Filter, Download, ShoppingCart, Truck, DollarSign, Calendar } from 'lucide-react';
import { Button, Input, Card, Badge } from '../../../../components/ui';
import { Button, Input, Card, Badge, Table } from '../../../../components/ui';
import type { TableColumn } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
const ProcurementPage: React.FC = () => {
@@ -124,6 +125,68 @@ const ProcurementPage: React.FC = () => {
return <Badge variant={config?.color as any}>{config?.text || status}</Badge>;
};
const columns: TableColumn[] = [
{
key: 'id',
title: 'Orden',
dataIndex: 'id',
render: (value, record: any) => (
<div>
<div className="text-sm font-medium text-[var(--text-primary)]">{value}</div>
{record.notes && (
<div className="text-xs text-[var(--text-tertiary)]">{record.notes}</div>
)}
</div>
),
},
{
key: 'supplier',
title: 'Proveedor',
dataIndex: 'supplier',
},
{
key: 'status',
title: 'Estado',
dataIndex: 'status',
render: (value) => getStatusBadge(value),
},
{
key: 'orderDate',
title: 'Fecha Pedido',
dataIndex: 'orderDate',
render: (value) => new Date(value).toLocaleDateString('es-ES'),
},
{
key: 'deliveryDate',
title: 'Fecha Entrega',
dataIndex: 'deliveryDate',
render: (value) => new Date(value).toLocaleDateString('es-ES'),
},
{
key: 'totalAmount',
title: 'Monto Total',
dataIndex: 'totalAmount',
render: (value) => `${value.toLocaleString()}`,
},
{
key: 'paymentStatus',
title: 'Pago',
dataIndex: 'paymentStatus',
render: (value) => getPaymentStatusBadge(value),
},
{
key: 'actions',
title: 'Acciones',
align: 'right' as const,
render: () => (
<div className="flex space-x-2">
<Button variant="outline" size="sm">Ver</Button>
<Button variant="outline" size="sm">Editar</Button>
</div>
),
},
];
const stats = {
totalOrders: mockPurchaseOrders.length,
pendingOrders: mockPurchaseOrders.filter(o => o.status === 'pending').length,
@@ -253,75 +316,15 @@ const ProcurementPage: React.FC = () => {
{/* Tab Content */}
{activeTab === 'orders' && (
<Card>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-[var(--bg-secondary)]">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Orden
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Proveedor
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Estado
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Fecha Pedido
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Fecha Entrega
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Monto Total
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Pago
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Acciones
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{mockPurchaseOrders.map((order) => (
<tr key={order.id} className="hover:bg-[var(--bg-secondary)]">
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-[var(--text-primary)]">{order.id}</div>
{order.notes && (
<div className="text-xs text-[var(--text-tertiary)]">{order.notes}</div>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{order.supplier}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getStatusBadge(order.status)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{new Date(order.orderDate).toLocaleDateString('es-ES')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{new Date(order.deliveryDate).toLocaleDateString('es-ES')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-[var(--text-primary)]">
{order.totalAmount.toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getPaymentStatusBadge(order.paymentStatus)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex space-x-2">
<Button variant="outline" size="sm">Ver</Button>
<Button variant="outline" size="sm">Editar</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<Card className="p-6">
<Table
columns={columns}
data={mockPurchaseOrders}
rowKey="id"
hover={true}
variant="default"
size="md"
/>
</Card>
)}

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Plus, Calendar, Clock, Users, AlertCircle } from 'lucide-react';
import { Button, Card, Badge } from '../../../../components/ui';
import { Button, Card, Badge, Table } from '../../../../components/ui';
import type { TableColumn } from '../../../../components/ui';
import { PageHeader } from '../../../../components/layout';
import { ProductionSchedule, BatchTracker, QualityControl } from '../../../../components/domain/production';
@@ -76,6 +77,86 @@ const ProductionPage: React.FC = () => {
return <Badge variant={config.color as any}>{config.text}</Badge>;
};
const columns: TableColumn[] = [
{
key: 'recipeName',
title: 'Receta',
dataIndex: 'recipeName',
render: (value) => (
<div className="text-sm font-medium text-[var(--text-primary)]">{value}</div>
),
},
{
key: 'quantity',
title: 'Cantidad',
dataIndex: 'quantity',
render: (value) => `${value} unidades`,
},
{
key: 'status',
title: 'Estado',
dataIndex: 'status',
render: (value) => getStatusBadge(value),
},
{
key: 'priority',
title: 'Prioridad',
dataIndex: 'priority',
render: (value) => getPriorityBadge(value),
},
{
key: 'assignedTo',
title: 'Asignado a',
dataIndex: 'assignedTo',
render: (value) => (
<div className="flex items-center">
<Users className="h-4 w-4 text-[var(--text-tertiary)] mr-2" />
<span className="text-sm text-[var(--text-primary)]">{value}</span>
</div>
),
},
{
key: 'progress',
title: 'Progreso',
dataIndex: 'progress',
render: (value) => (
<div className="flex items-center">
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mr-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${value}%` }}
></div>
</div>
<span className="text-sm text-[var(--text-primary)]">{value}%</span>
</div>
),
},
{
key: 'estimatedCompletion',
title: 'Tiempo Estimado',
dataIndex: 'estimatedCompletion',
render: (value) => new Date(value).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
}),
},
{
key: 'actions',
title: 'Acciones',
align: 'right' as const,
render: () => (
<div className="flex space-x-2">
<Button variant="outline" size="sm">
Ver
</Button>
<Button variant="outline" size="sm">
Editar
</Button>
</div>
),
},
];
return (
<div className="p-6 space-y-6">
<PageHeader
@@ -216,87 +297,14 @@ const ProductionPage: React.FC = () => {
</div>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-[var(--bg-secondary)]">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Receta
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Cantidad
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Estado
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Prioridad
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Asignado a
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Progreso
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Tiempo Estimado
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase tracking-wider">
Acciones
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{mockProductionOrders.map((order) => (
<tr key={order.id} className="hover:bg-[var(--bg-secondary)]">
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-[var(--text-primary)]">{order.recipeName}</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{order.quantity} unidades
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getStatusBadge(order.status)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
{getPriorityBadge(order.priority)}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<Users className="h-4 w-4 text-[var(--text-tertiary)] mr-2" />
<span className="text-sm text-[var(--text-primary)]">{order.assignedTo}</span>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="w-full bg-[var(--bg-quaternary)] rounded-full h-2 mr-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${order.progress}%` }}
></div>
</div>
<span className="text-sm text-[var(--text-primary)]">{order.progress}%</span>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-[var(--text-primary)]">
{new Date(order.estimatedCompletion).toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
})}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<Button variant="outline" size="sm" className="mr-2">
Ver
</Button>
<Button variant="outline" size="sm">
Editar
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<Table
columns={columns}
data={mockProductionOrders}
rowKey="id"
hover={true}
variant="default"
size="md"
/>
</div>
</Card>
)}

View File

@@ -95,33 +95,28 @@ const RegisterPage: React.FC = () => {
<Card className="p-8">
{/* Progress indicator */}
<div className="mb-8">
<div className="flex items-center">
{[1, 2, 3].map((stepNumber) => (
<div key={stepNumber} className="flex items-center">
<div className="flex items-start justify-between">
{[
{ step: 1, label: 'Datos personales' },
{ step: 2, label: 'Información empresarial' },
{ step: 3, label: 'Crear cuenta' }
].map((stepInfo) => (
<div key={stepInfo.step} className="flex flex-col items-center">
<div
className={`flex items-center justify-center w-8 h-8 rounded-full ${
step >= stepNumber
step >= stepInfo.step
? 'bg-[var(--color-primary)] text-white'
: 'bg-[var(--bg-quaternary)] text-[var(--text-tertiary)]'
}`}
>
{stepNumber}
{stepInfo.step}
</div>
{stepNumber < 3 && (
<div
className={`flex-1 h-0.5 mx-4 ${
step > stepNumber ? 'bg-[var(--color-primary)]' : 'bg-[var(--bg-quaternary)]'
}`}
/>
)}
<span className="mt-2 text-xs text-[var(--text-secondary)] text-center max-w-[80px]">
{stepInfo.label}
</span>
</div>
))}
</div>
<div className="flex justify-between mt-2 text-xs text-[var(--text-secondary)]">
<span>Datos personales</span>
<span>Información empresarial</span>
<span>Crear cuenta</span>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
@@ -220,15 +215,16 @@ const RegisterPage: React.FC = () => {
</label>
<Select
value={formData.companyType}
onValueChange={(value) => handleInputChange('companyType', value)}
>
<option value="">Selecciona el tipo</option>
<option value="traditional">Panadería tradicional</option>
<option value="artisan">Panadería artesanal</option>
<option value="industrial">Panadería industrial</option>
<option value="bakery-cafe">Panadería-cafetería</option>
<option value="specialty">Panadería especializada</option>
</Select>
onChange={(value) => handleInputChange('companyType', value as string)}
placeholder="Selecciona el tipo"
options={[
{ value: "traditional", label: "Panadería tradicional" },
{ value: "artisan", label: "Panadería artesanal" },
{ value: "industrial", label: "Panadería industrial" },
{ value: "bakery-cafe", label: "Panadería-cafetería" },
{ value: "specialty", label: "Panadería especializada" }
]}
/>
</div>
<div>
@@ -237,15 +233,16 @@ const RegisterPage: React.FC = () => {
</label>
<Select
value={formData.employeeCount}
onValueChange={(value) => handleInputChange('employeeCount', value)}
>
<option value="">Selecciona el rango</option>
<option value="1">Solo yo</option>
<option value="2-5">2-5 empleados</option>
<option value="6-15">6-15 empleados</option>
<option value="16-50">16-50 empleados</option>
<option value="51+">Más de 50 empleados</option>
</Select>
onChange={(value) => handleInputChange('employeeCount', value as string)}
placeholder="Selecciona el rango"
options={[
{ value: "1", label: "Solo yo" },
{ value: "2-5", label: "2-5 empleados" },
{ value: "6-15", label: "6-15 empleados" },
{ value: "16-50", label: "16-50 empleados" },
{ value: "51+", label: "Más de 50 empleados" }
]}
/>
</div>
</div>

View File

@@ -126,6 +126,7 @@
/* Layout */
--container-max-width: 1280px;
--sidebar-width: 280px;
--sidebar-collapsed-width: 64px;
--header-height: 64px;
--footer-height: 56px;
}