206 lines
9.5 KiB
TypeScript
206 lines
9.5 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { Plus, Pencil, Trash2, Box, RefreshCw } from 'lucide-react';
|
|
import { Button } from '../Button';
|
|
import { Modal } from '../Modal';
|
|
import { Input } from '../Input';
|
|
import * as geometryService from '../../services/geometryTypeService';
|
|
import type { GeometryType } from '../../types';
|
|
import { useAuth } from '../../context/useAuth';
|
|
|
|
export const GeometrySettings: React.FC = () => {
|
|
const { appUser } = useAuth();
|
|
const [types, setTypes] = useState<GeometryType[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [editingItem, setEditingItem] = useState<GeometryType | null>(null);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const [form, setForm] = useState({
|
|
name: '',
|
|
efficiencyLoss: '20'
|
|
});
|
|
|
|
const fetchTypes = React.useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await geometryService.getAllTypes();
|
|
setTypes(response.data);
|
|
} catch (error) {
|
|
console.error('Error fetching geometry types', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (appUser) {
|
|
fetchTypes();
|
|
}
|
|
}, [appUser, fetchTypes]);
|
|
|
|
const handleOpenModal = (item?: GeometryType) => {
|
|
if (item) {
|
|
setEditingItem(item);
|
|
setForm({ name: item.name, efficiencyLoss: (item.efficiencyLoss ?? 0).toString() });
|
|
} else {
|
|
setEditingItem(null);
|
|
setForm({ name: '', efficiencyLoss: '20' });
|
|
}
|
|
setIsModalOpen(true);
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setSaving(true);
|
|
try {
|
|
const payload = {
|
|
name: form.name,
|
|
efficiencyLoss: parseFloat(form.efficiencyLoss) || 0
|
|
};
|
|
|
|
if (editingItem) {
|
|
await geometryService.updateType(editingItem.id || editingItem._id!, payload);
|
|
} else {
|
|
await geometryService.createType(payload);
|
|
}
|
|
setIsModalOpen(false);
|
|
fetchTypes();
|
|
} catch (error) {
|
|
console.error('Error saving type', error);
|
|
alert('Erro ao salvar tipo de geometria');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: string) => {
|
|
if (!confirm('Tem certeza que deseja excluir? Isso pode afetar peças criadas com este tipo.')) return;
|
|
try {
|
|
await geometryService.deleteType(id);
|
|
fetchTypes();
|
|
} catch (error) {
|
|
console.error('Error deleting type', error);
|
|
}
|
|
};
|
|
|
|
const handleRestoreDefaults = async () => {
|
|
if (!confirm('Isso irá apagar todos os tipos atuais e restaurar a lista padrão com 20% de perda. Continuar?')) return;
|
|
setLoading(true);
|
|
try {
|
|
await geometryService.restoreDefaults();
|
|
fetchTypes();
|
|
} catch (error) {
|
|
console.error('Error restoring defaults', error);
|
|
alert('Erro ao restaurar padrões');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6 animate-in fade-in slide-in-from-left-4 duration-300">
|
|
<div className="flex justify-between items-center bg-surface-soft/30 p-6 rounded-2xl border border-border/20">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
|
|
<Box size={24} className="text-primary" />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-xl font-bold text-text-main">Tipos de Geometria/Peças</h2>
|
|
<p className="text-sm text-text-muted">Gerencie a lista padrão de peças e suas perdas de eficiência</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="ghost" onClick={handleRestoreDefaults} title="Restaurar lista original">
|
|
<RefreshCw size={20} className={loading ? 'animate-spin' : ''} />
|
|
</Button>
|
|
<Button onClick={() => handleOpenModal()}>
|
|
<Plus className="w-5 h-5 mr-2" /> Novo Tipo
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-surface rounded-2xl border border-border/40 overflow-hidden shadow-soft">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-20">
|
|
<RefreshCw size={32} className="animate-spin text-primary" />
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead>
|
|
<tr className="border-b border-border/40 bg-surface-soft">
|
|
<th className="text-left px-6 py-4 text-xs font-bold text-text-muted uppercase tracking-wider">Nome da Geometria</th>
|
|
<th className="text-left px-6 py-4 text-xs font-bold text-text-muted uppercase tracking-wider">Perda de Eficiência (%)</th>
|
|
<th className="text-right px-6 py-4 text-xs font-bold text-text-muted uppercase tracking-wider">Ações</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-border/40">
|
|
{types.map((type) => (
|
|
<tr key={type.id || type._id} className="hover:bg-surface-hover transition-colors">
|
|
<td className="px-6 py-4 font-semibold text-text-main">{type.name}</td>
|
|
<td className="px-6 py-4">
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20">
|
|
{type.efficiencyLoss}%
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-right flex justify-end gap-2">
|
|
<button
|
|
onClick={() => handleOpenModal(type)}
|
|
className="p-2 text-text-muted hover:text-primary hover:bg-primary/5 rounded-lg transition-all"
|
|
title="Editar"
|
|
>
|
|
<Pencil size={18} />
|
|
</button>
|
|
<button
|
|
onClick={() => handleDelete(type.id || type._id!)}
|
|
className="p-2 text-text-muted hover:text-error hover:bg-error/5 rounded-lg transition-all"
|
|
title="Excluir"
|
|
>
|
|
<Trash2 size={18} />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
{types.length === 0 && (
|
|
<tr>
|
|
<td colSpan={3} className="px-6 py-12 text-center text-text-muted">
|
|
Nenhum tipo cadastrado.
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} title={editingItem ? 'Editar Tipo' : 'Novo Tipo de Geometria'}>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<Input
|
|
name="name"
|
|
label="Nome (Ex: Vigas médias)"
|
|
value={form.name}
|
|
onChange={e => setForm({ ...form, name: e.target.value })}
|
|
required
|
|
/>
|
|
<Input
|
|
name="efficiencyLoss"
|
|
label="Perda de Eficiência (%)"
|
|
type="number"
|
|
step="0.1"
|
|
min="0"
|
|
max="100"
|
|
value={form.efficiencyLoss}
|
|
onChange={e => setForm({ ...form, efficiencyLoss: e.target.value })}
|
|
required
|
|
/>
|
|
<div className="flex justify-end gap-2 mt-6">
|
|
<Button type="button" variant="ghost" onClick={() => setIsModalOpen(false)} disabled={saving}>Cancelar</Button>
|
|
<Button type="submit" disabled={saving}>{saving ? 'Salvando...' : 'Salvar'}</Button>
|
|
</div>
|
|
</form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|