Improve the frontend 2
This commit is contained in:
@@ -8,19 +8,26 @@ import { PageHeader } from '../../../../components/layout';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { Equipment } from '../../../../api/types/equipment';
|
||||
import { EquipmentModal } from '../../../../components/domain/equipment/EquipmentModal';
|
||||
import { useEquipment, useCreateEquipment, useUpdateEquipment } from '../../../../api/hooks/equipment';
|
||||
import { DeleteEquipmentModal } from '../../../../components/domain/equipment/DeleteEquipmentModal';
|
||||
import { MaintenanceHistoryModal } from '../../../../components/domain/equipment/MaintenanceHistoryModal';
|
||||
import { ScheduleMaintenanceModal, type MaintenanceScheduleData } from '../../../../components/domain/equipment/ScheduleMaintenanceModal';
|
||||
import { useEquipment, useCreateEquipment, useUpdateEquipment, useDeleteEquipment, useHardDeleteEquipment } from '../../../../api/hooks/equipment';
|
||||
|
||||
const MaquinariaPage: React.FC = () => {
|
||||
const { t } = useTranslation(['equipment', 'common']);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [typeFilter, setTypeFilter] = useState('');
|
||||
const [selectedItem, setSelectedItem] = useState<Equipment | null>(null);
|
||||
const [showMaintenanceModal, setShowMaintenanceModal] = useState(false);
|
||||
const [showEquipmentModal, setShowEquipmentModal] = useState(false);
|
||||
const [equipmentModalMode, setEquipmentModalMode] = useState<'view' | 'edit' | 'create'>('create');
|
||||
const [selectedEquipment, setSelectedEquipment] = useState<Equipment | null>(null);
|
||||
|
||||
// New modal states
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showHistoryModal, setShowHistoryModal] = useState(false);
|
||||
const [showScheduleModal, setShowScheduleModal] = useState(false);
|
||||
const [equipmentForAction, setEquipmentForAction] = useState<Equipment | null>(null);
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
|
||||
@@ -29,9 +36,11 @@ const MaquinariaPage: React.FC = () => {
|
||||
is_active: true
|
||||
});
|
||||
|
||||
// Mutations for create and update
|
||||
// Mutations for create, update, and delete
|
||||
const createEquipmentMutation = useCreateEquipment(tenantId);
|
||||
const updateEquipmentMutation = useUpdateEquipment(tenantId);
|
||||
const deleteEquipmentMutation = useDeleteEquipment(tenantId);
|
||||
const hardDeleteEquipmentMutation = useHardDeleteEquipment(tenantId);
|
||||
|
||||
const handleCreateEquipment = () => {
|
||||
setSelectedEquipment({
|
||||
@@ -73,19 +82,58 @@ const MaquinariaPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleScheduleMaintenance = (equipmentId: string) => {
|
||||
console.log('Schedule maintenance for equipment:', equipmentId);
|
||||
// Implementation would go here
|
||||
const handleScheduleMaintenance = (equipment: Equipment) => {
|
||||
setEquipmentForAction(equipment);
|
||||
setShowScheduleModal(true);
|
||||
};
|
||||
|
||||
const handleAcknowledgeAlert = (equipmentId: string, alertId: string) => {
|
||||
console.log('Acknowledge alert:', alertId, 'for equipment:', equipmentId);
|
||||
// Implementation would go here
|
||||
const handleScheduleMaintenanceSubmit = async (equipmentId: string, maintenanceData: MaintenanceScheduleData) => {
|
||||
try {
|
||||
// Update next maintenance date based on scheduled date
|
||||
await updateEquipmentMutation.mutateAsync({
|
||||
equipmentId: equipmentId,
|
||||
equipmentData: {
|
||||
nextMaintenance: maintenanceData.scheduledDate
|
||||
} as Partial<Equipment>
|
||||
});
|
||||
setShowScheduleModal(false);
|
||||
setEquipmentForAction(null);
|
||||
} catch (error) {
|
||||
console.error('Error scheduling maintenance:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewMaintenanceHistory = (equipmentId: string) => {
|
||||
console.log('View maintenance history for equipment:', equipmentId);
|
||||
// Implementation would go here
|
||||
const handleViewMaintenanceHistory = (equipment: Equipment) => {
|
||||
setEquipmentForAction(equipment);
|
||||
setShowHistoryModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteEquipment = (equipment: Equipment) => {
|
||||
setEquipmentForAction(equipment);
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const handleSoftDelete = async (equipmentId: string) => {
|
||||
try {
|
||||
await deleteEquipmentMutation.mutateAsync(equipmentId);
|
||||
setShowDeleteModal(false);
|
||||
setEquipmentForAction(null);
|
||||
} catch (error) {
|
||||
console.error('Error deleting equipment:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleHardDelete = async (equipmentId: string) => {
|
||||
try {
|
||||
await hardDeleteEquipmentMutation.mutateAsync(equipmentId);
|
||||
setShowDeleteModal(false);
|
||||
setEquipmentForAction(null);
|
||||
} catch (error) {
|
||||
console.error('Error hard deleting equipment:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveEquipment = async (equipmentData: Equipment) => {
|
||||
@@ -200,13 +248,9 @@ const MaquinariaPage: React.FC = () => {
|
||||
];
|
||||
|
||||
const handleShowMaintenanceDetails = (equipment: Equipment) => {
|
||||
setSelectedItem(equipment);
|
||||
setShowMaintenanceModal(true);
|
||||
};
|
||||
|
||||
const handleCloseMaintenanceModal = () => {
|
||||
setShowMaintenanceModal(false);
|
||||
setSelectedItem(null);
|
||||
setSelectedEquipment(equipment);
|
||||
setEquipmentModalMode('view');
|
||||
setShowEquipmentModal(true);
|
||||
};
|
||||
|
||||
// Loading state
|
||||
@@ -336,23 +380,25 @@ const MaquinariaPage: React.FC = () => {
|
||||
priority: 'primary',
|
||||
onClick: () => handleShowMaintenanceDetails(equipment)
|
||||
},
|
||||
{
|
||||
label: t('actions.edit'),
|
||||
icon: Edit,
|
||||
priority: 'secondary',
|
||||
onClick: () => handleEditEquipment(equipment.id)
|
||||
},
|
||||
{
|
||||
label: t('actions.view_history'),
|
||||
icon: History,
|
||||
priority: 'secondary',
|
||||
onClick: () => handleViewMaintenanceHistory(equipment.id)
|
||||
onClick: () => handleViewMaintenanceHistory(equipment)
|
||||
},
|
||||
{
|
||||
label: t('actions.schedule_maintenance'),
|
||||
icon: Wrench,
|
||||
priority: 'secondary',
|
||||
onClick: () => handleScheduleMaintenance(equipment.id)
|
||||
highlighted: true,
|
||||
onClick: () => handleScheduleMaintenance(equipment)
|
||||
},
|
||||
{
|
||||
label: t('actions.delete'),
|
||||
icon: Trash2,
|
||||
priority: 'secondary',
|
||||
destructive: true,
|
||||
onClick: () => handleDeleteEquipment(equipment)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -372,183 +418,7 @@ const MaquinariaPage: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Maintenance Details Modal */}
|
||||
{selectedItem && showMaintenanceModal && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4 overflow-y-auto">
|
||||
<div className="bg-[var(--bg-primary)] rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto my-8">
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="flex items-center justify-between mb-4 sm:mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg sm:text-xl font-semibold text-[var(--text-primary)]">
|
||||
{selectedItem.name}
|
||||
</h2>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
{selectedItem.model} - {selectedItem.serialNumber}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCloseMaintenanceModal}
|
||||
className="text-[var(--text-tertiary)] hover:text-[var(--text-primary)] p-1"
|
||||
>
|
||||
<svg className="w-5 h-5 sm:w-6 sm:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
{/* Equipment Status */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||
<div className="p-3 sm:p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<h3 className="font-medium text-[var(--text-primary)] mb-2 text-sm sm:text-base">{t('fields.status')}</h3>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div
|
||||
className="w-2 h-2 sm:w-3 sm:h-3 rounded-full"
|
||||
style={{ backgroundColor: getStatusConfig(selectedItem.status).color }}
|
||||
/>
|
||||
<span className="text-[var(--text-primary)] text-sm sm:text-base">
|
||||
{t(`equipment_status.${selectedItem.status}`)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 sm:p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<h3 className="font-medium text-[var(--text-primary)] mb-2 text-sm sm:text-base">{t('fields.efficiency')}</h3>
|
||||
<div className="text-lg sm:text-2xl font-bold text-[var(--text-primary)]">
|
||||
{selectedItem.efficiency}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Maintenance Information */}
|
||||
<div className="p-3 sm:p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<h3 className="font-medium text-[var(--text-primary)] mb-3 sm:mb-4 text-sm sm:text-base">{t('maintenance.title')}</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div>
|
||||
<p className="text-xs sm:text-sm text-[var(--text-secondary)]">{t('maintenance.last')}</p>
|
||||
<p className="text-[var(--text-primary)] text-sm sm:text-base">
|
||||
{new Date(selectedItem.lastMaintenance).toLocaleDateString('es-ES')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs sm:text-sm text-[var(--text-secondary)]">{t('maintenance.next')}</p>
|
||||
<p className={`font-medium ${(new Date(selectedItem.nextMaintenance).getTime() < new Date().getTime()) ? 'text-red-500' : 'text-[var(--text-primary)]'} text-sm sm:text-base`}>
|
||||
{new Date(selectedItem.nextMaintenance).toLocaleDateString('es-ES')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs sm:text-sm text-[var(--text-secondary)]">{t('maintenance.interval')}</p>
|
||||
<p className="text-[var(--text-primary)] text-sm sm:text-base">
|
||||
{selectedItem.maintenanceInterval} {t('common:units.days')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{new Date(selectedItem.nextMaintenance).getTime() < new Date().getTime() && (
|
||||
<div className="mt-3 p-2 bg-red-50 dark:bg-red-900/20 rounded border-l-2 border-red-500">
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertTriangle className="w-4 h-4 text-red-500" />
|
||||
<span className="text-xs sm:text-sm font-medium text-red-700 dark:text-red-300">
|
||||
{t('maintenance.overdue')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Active Alerts */}
|
||||
{selectedItem.alerts.filter(a => !a.acknowledged).length > 0 && (
|
||||
<div className="p-3 sm:p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<h3 className="font-medium text-[var(--text-primary)] mb-3 sm:mb-4 text-sm sm:text-base">{t('alerts.title')}</h3>
|
||||
<div className="space-y-2 sm:space-y-3">
|
||||
{selectedItem.alerts.filter(a => !a.acknowledged).map((alert) => (
|
||||
<div
|
||||
key={alert.id}
|
||||
className={`p-2 sm:p-3 rounded border-l-2 ${
|
||||
alert.type === 'critical' ? 'bg-red-50 border-red-500 dark:bg-red-900/20' :
|
||||
alert.type === 'warning' ? 'bg-orange-50 border-orange-500 dark:bg-orange-900/20' :
|
||||
'bg-blue-50 border-blue-500 dark:bg-blue-900/20'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertTriangle className={`w-3 h-3 sm:w-4 sm:h-4 ${
|
||||
alert.type === 'critical' ? 'text-red-500' :
|
||||
alert.type === 'warning' ? 'text-orange-500' : 'text-blue-500'
|
||||
}`} />
|
||||
<span className="font-medium text-[var(--text-primary)] text-xs sm:text-sm">
|
||||
{alert.message}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-[var(--text-secondary)] hidden sm:block">
|
||||
{new Date(alert.timestamp).toLocaleString('es-ES')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Maintenance History */}
|
||||
<div className="p-3 sm:p-4 bg-[var(--bg-secondary)] rounded-lg">
|
||||
<h3 className="font-medium text-[var(--text-primary)] mb-3 sm:mb-4 text-sm sm:text-base">{t('maintenance.history')}</h3>
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{selectedItem.maintenanceHistory.map((history) => (
|
||||
<div key={history.id} className="border-b border-[var(--border-primary)] pb-2 sm:pb-3 last:border-0 last:pb-0">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 className="font-medium text-[var(--text-primary)] text-sm">{history.description}</h4>
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
{new Date(history.date).toLocaleDateString('es-ES')} - {history.technician}
|
||||
</p>
|
||||
</div>
|
||||
<span className="px-1.5 py-0.5 sm:px-2 sm:py-1 bg-[var(--bg-tertiary)] text-xs rounded">
|
||||
{t(`maintenance.type.${history.type}`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 sm:mt-2 flex flex-wrap gap-2">
|
||||
<span className="text-xs">
|
||||
<span className="text-[var(--text-secondary)]">{t('common:actions.cost')}:</span>
|
||||
<span className="font-medium text-[var(--text-primary)]"> €{history.cost}</span>
|
||||
</span>
|
||||
<span className="text-xs">
|
||||
<span className="text-[var(--text-secondary)]">{t('fields.uptime')}:</span>
|
||||
<span className="font-medium text-[var(--text-primary)]"> {history.downtime}h</span>
|
||||
</span>
|
||||
</div>
|
||||
{history.partsUsed.length > 0 && (
|
||||
<div className="mt-1 sm:mt-2">
|
||||
<span className="text-xs text-[var(--text-secondary)]">{t('fields.parts')}:</span>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{history.partsUsed.map((part, index) => (
|
||||
<span key={index} className="px-1.5 py-0.5 sm:px-2 sm:py-1 bg-[var(--bg-tertiary)] text-xs rounded">
|
||||
{part}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 sm:space-x-3 mt-4 sm:mt-6">
|
||||
<Button variant="outline" size="sm" onClick={handleCloseMaintenanceModal}>
|
||||
{t('common:actions.close')}
|
||||
</Button>
|
||||
<Button variant="primary" size="sm" onClick={() => selectedItem && handleScheduleMaintenance(selectedItem.id)}>
|
||||
{t('actions.schedule_maintenance')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Equipment Modal */}
|
||||
{/* Equipment Modal - Used for View Details, Edit, and Create */}
|
||||
{showEquipmentModal && (
|
||||
<EquipmentModal
|
||||
isOpen={showEquipmentModal}
|
||||
@@ -561,6 +431,47 @@ const MaquinariaPage: React.FC = () => {
|
||||
mode={equipmentModalMode}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Equipment Modal */}
|
||||
{showDeleteModal && equipmentForAction && (
|
||||
<DeleteEquipmentModal
|
||||
isOpen={showDeleteModal}
|
||||
onClose={() => {
|
||||
setShowDeleteModal(false);
|
||||
setEquipmentForAction(null);
|
||||
}}
|
||||
equipment={equipmentForAction}
|
||||
onSoftDelete={handleSoftDelete}
|
||||
onHardDelete={handleHardDelete}
|
||||
isLoading={deleteEquipmentMutation.isPending || hardDeleteEquipmentMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Maintenance History Modal */}
|
||||
{showHistoryModal && equipmentForAction && (
|
||||
<MaintenanceHistoryModal
|
||||
isOpen={showHistoryModal}
|
||||
onClose={() => {
|
||||
setShowHistoryModal(false);
|
||||
setEquipmentForAction(null);
|
||||
}}
|
||||
equipment={equipmentForAction}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Schedule Maintenance Modal */}
|
||||
{showScheduleModal && equipmentForAction && (
|
||||
<ScheduleMaintenanceModal
|
||||
isOpen={showScheduleModal}
|
||||
onClose={() => {
|
||||
setShowScheduleModal(false);
|
||||
setEquipmentForAction(null);
|
||||
}}
|
||||
equipment={equipmentForAction}
|
||||
onSchedule={handleScheduleMaintenanceSubmit}
|
||||
isLoading={updateEquipmentMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -187,7 +187,7 @@ const ProcurementPage: React.FC = () => {
|
||||
|
||||
const handleTriggerScheduler = async () => {
|
||||
try {
|
||||
await triggerSchedulerMutation.mutateAsync({ tenantId });
|
||||
await triggerSchedulerMutation.mutateAsync(tenantId);
|
||||
toast.success('Scheduler ejecutado exitosamente');
|
||||
refetchPOs();
|
||||
} catch (error) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
export { default as ProcurementPage } from './ProcurementPage';
|
||||
export { default as ProcurementPage } from './ProcurementPage';
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, Euro, Loader, Trash2 } from 'lucide-react';
|
||||
import { Button, Badge, StatsGrid, StatusCard, getStatusColor, EditViewModal, AddModal, SearchAndFilter, DialogModal, type FilterConfig, EmptyState } from '../../../../components/ui';
|
||||
import { Plus, Building2, Phone, Mail, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, Euro, Loader, Trash2, DollarSign, Package } from 'lucide-react';
|
||||
import { Button, Badge, StatsGrid, StatusCard, getStatusColor, EditViewModal, AddModal, SearchAndFilter, DialogModal, Modal, ModalHeader, ModalBody, type FilterConfig, EmptyState } from '../../../../components/ui';
|
||||
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { SupplierStatus, SupplierType, PaymentTerms } from '../../../../api/types/suppliers';
|
||||
import { useSuppliers, useSupplierStatistics, useCreateSupplier, useUpdateSupplier, useApproveSupplier, useDeleteSupplier, useHardDeleteSupplier } from '../../../../api/hooks/suppliers';
|
||||
import { useSuppliers, useSupplierStatistics, useCreateSupplier, useUpdateSupplier, useApproveSupplier, useDeleteSupplier, useHardDeleteSupplier, useSupplierPriceLists, useCreateSupplierPriceList, useUpdateSupplierPriceList, useDeleteSupplierPriceList } from '../../../../api/hooks/suppliers';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { statusColors } from '../../../../styles/colors';
|
||||
import { DeleteSupplierModal } from '../../../../components/domain/suppliers';
|
||||
import { DeleteSupplierModal, SupplierPriceListViewModal, PriceListModal } from '../../../../components/domain/suppliers';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
const SuppliersPage: React.FC = () => {
|
||||
const [activeTab] = useState('all');
|
||||
@@ -23,6 +24,9 @@ const SuppliersPage: React.FC = () => {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showApprovalModal, setShowApprovalModal] = useState(false);
|
||||
const [supplierToApprove, setSupplierToApprove] = useState<any>(null);
|
||||
const [showPriceListView, setShowPriceListView] = useState(false);
|
||||
const [showAddPrice, setShowAddPrice] = useState(false);
|
||||
const [priceListSupplier, setPriceListSupplier] = useState<any>(null);
|
||||
|
||||
// Get tenant ID from tenant store (preferred) or auth user (fallback)
|
||||
const currentTenant = useCurrentTenant();
|
||||
@@ -48,6 +52,7 @@ const SuppliersPage: React.FC = () => {
|
||||
|
||||
const suppliers = suppliersData || [];
|
||||
const { t } = useTranslation(['suppliers', 'common']);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Mutation hooks
|
||||
const createSupplierMutation = useCreateSupplier();
|
||||
@@ -56,6 +61,21 @@ const SuppliersPage: React.FC = () => {
|
||||
const softDeleteMutation = useDeleteSupplier();
|
||||
const hardDeleteMutation = useHardDeleteSupplier();
|
||||
|
||||
// Price list hooks
|
||||
const {
|
||||
data: priceListsData,
|
||||
isLoading: priceListsLoading,
|
||||
isRefetching: isRefetchingPriceLists
|
||||
} = useSupplierPriceLists(
|
||||
tenantId,
|
||||
priceListSupplier?.id || '',
|
||||
!!priceListSupplier?.id && showPriceListView
|
||||
);
|
||||
|
||||
const createPriceListMutation = useCreateSupplierPriceList();
|
||||
const updatePriceListMutation = useUpdateSupplierPriceList();
|
||||
const deletePriceListMutation = useDeleteSupplierPriceList();
|
||||
|
||||
// Delete handlers
|
||||
const handleSoftDelete = async (supplierId: string) => {
|
||||
await softDeleteMutation.mutateAsync({ tenantId, supplierId });
|
||||
@@ -65,6 +85,27 @@ const SuppliersPage: React.FC = () => {
|
||||
return await hardDeleteMutation.mutateAsync({ tenantId, supplierId });
|
||||
};
|
||||
|
||||
// Price list handlers
|
||||
const handlePriceListSaveComplete = async () => {
|
||||
if (!tenantId || !priceListSupplier?.id) return;
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['supplier-price-lists', tenantId, priceListSupplier.id]
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddPriceSubmit = async (priceListData: any) => {
|
||||
if (!priceListSupplier) return;
|
||||
|
||||
await createPriceListMutation.mutateAsync({
|
||||
tenantId,
|
||||
supplierId: priceListSupplier.id,
|
||||
priceListData
|
||||
});
|
||||
|
||||
// Close the add modal
|
||||
setShowAddPrice(false);
|
||||
};
|
||||
|
||||
const getSupplierStatusConfig = (status: SupplierStatus) => {
|
||||
const statusConfig = {
|
||||
[SupplierStatus.ACTIVE]: { text: t(`suppliers:status.${status.toLowerCase()}`), icon: CheckCircle },
|
||||
@@ -274,6 +315,18 @@ const SuppliersPage: React.FC = () => {
|
||||
setShowForm(true);
|
||||
}
|
||||
},
|
||||
// Manage products action
|
||||
{
|
||||
label: t('suppliers:actions.manage_products'),
|
||||
icon: Package,
|
||||
variant: 'outline',
|
||||
priority: 'secondary',
|
||||
highlighted: true,
|
||||
onClick: () => {
|
||||
setPriceListSupplier(supplier);
|
||||
setShowPriceListView(true);
|
||||
}
|
||||
},
|
||||
// Approval action - Only show for pending suppliers + admin/super_admin
|
||||
...(supplier.status === SupplierStatus.PENDING_APPROVAL &&
|
||||
(user?.role === 'admin' || user?.role === 'super_admin')
|
||||
@@ -769,7 +822,7 @@ const SuppliersPage: React.FC = () => {
|
||||
placeholder: t('suppliers:placeholders.notes')
|
||||
}
|
||||
]
|
||||
}] : [])
|
||||
}] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -942,6 +995,55 @@ const SuppliersPage: React.FC = () => {
|
||||
}}
|
||||
loading={approveSupplierMutation.isPending}
|
||||
/>
|
||||
|
||||
{/* Price List View Modal */}
|
||||
{priceListSupplier && (
|
||||
<SupplierPriceListViewModal
|
||||
isOpen={showPriceListView}
|
||||
onClose={() => {
|
||||
setShowPriceListView(false);
|
||||
setPriceListSupplier(null);
|
||||
}}
|
||||
supplier={priceListSupplier}
|
||||
priceLists={priceListsData || []}
|
||||
loading={priceListsLoading}
|
||||
tenantId={tenantId}
|
||||
onAddPrice={() => setShowAddPrice(true)}
|
||||
onEditPrice={async (priceId, updateData) => {
|
||||
await updatePriceListMutation.mutateAsync({
|
||||
tenantId,
|
||||
supplierId: priceListSupplier.id,
|
||||
priceListId: priceId,
|
||||
priceListData: updateData
|
||||
});
|
||||
}}
|
||||
onDeletePrice={async (priceId) => {
|
||||
await deletePriceListMutation.mutateAsync({
|
||||
tenantId,
|
||||
supplierId: priceListSupplier.id,
|
||||
priceListId: priceId
|
||||
});
|
||||
}}
|
||||
waitForRefetch={true}
|
||||
isRefetching={isRefetchingPriceLists}
|
||||
onSaveComplete={handlePriceListSaveComplete}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Add Price Modal */}
|
||||
{priceListSupplier && (
|
||||
<PriceListModal
|
||||
isOpen={showAddPrice}
|
||||
onClose={() => setShowAddPrice(false)}
|
||||
onSave={handleAddPriceSubmit}
|
||||
mode="create"
|
||||
loading={createPriceListMutation.isPending}
|
||||
excludeProductIds={priceListsData?.map(p => p.inventory_product_id) || []}
|
||||
waitForRefetch={true}
|
||||
isRefetching={isRefetchingPriceLists}
|
||||
onSaveComplete={handlePriceListSaveComplete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user