194 lines
8.8 KiB
TypeScript
194 lines
8.8 KiB
TypeScript
|
|
import React, { useEffect, useState } from 'react';
|
||
|
|
import { supabase } from '../../services/supabase';
|
||
|
|
import { Printer } from '../../types';
|
||
|
|
import { Plus, Trash2, Save, Printer as PrinterIcon, Zap } from 'lucide-react';
|
||
|
|
|
||
|
|
const Printers: React.FC = () => {
|
||
|
|
const [printers, setPrinters] = useState<Printer[]>([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [isAdding, setIsAdding] = useState(false);
|
||
|
|
|
||
|
|
const [newPrinter, setNewPrinter] = useState<Partial<Printer>>({
|
||
|
|
name: '',
|
||
|
|
powerWatts: 350,
|
||
|
|
depreciationPerHour: 0
|
||
|
|
});
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchPrinters();
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const fetchPrinters = async () => {
|
||
|
|
try {
|
||
|
|
const { data, error } = await supabase.from('printers').select('*');
|
||
|
|
if (error) throw error;
|
||
|
|
|
||
|
|
const mapped = (data || []).map((item: any) => ({
|
||
|
|
id: item.id,
|
||
|
|
name: item.name,
|
||
|
|
powerWatts: item.power_watts,
|
||
|
|
depreciationPerHour: item.depreciation_per_hour
|
||
|
|
}));
|
||
|
|
|
||
|
|
setPrinters(mapped);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error fetching printers:', error);
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSave = async () => {
|
||
|
|
if (!newPrinter.name) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const payload = {
|
||
|
|
name: newPrinter.name,
|
||
|
|
power_watts: newPrinter.powerWatts,
|
||
|
|
depreciation_per_hour: newPrinter.depreciationPerHour,
|
||
|
|
user_id: (await supabase.auth.getUser()).data.user?.id
|
||
|
|
};
|
||
|
|
|
||
|
|
const { error } = await supabase.from('printers').insert([payload]);
|
||
|
|
if (error) throw error;
|
||
|
|
|
||
|
|
setIsAdding(false);
|
||
|
|
setNewPrinter({ name: '', powerWatts: 350, depreciationPerHour: 0 });
|
||
|
|
fetchPrinters();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error saving printer:', error);
|
||
|
|
alert('Error saving printer');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleDelete = async (id: string) => {
|
||
|
|
if (!confirm('Are you sure?')) return;
|
||
|
|
try {
|
||
|
|
const { error } = await supabase.from('printers').delete().eq('id', id);
|
||
|
|
if (error) throw error;
|
||
|
|
fetchPrinters();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error deleting printer:', error);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
<div className="flex justify-between items-center">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-cyan-600">
|
||
|
|
Printers
|
||
|
|
</h1>
|
||
|
|
<p className="text-muted-foreground mt-1">Manage your 3D printers configuration.</p>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={() => setIsAdding(!isAdding)}
|
||
|
|
className="bg-primary hover:bg-primary/90 text-primary-foreground px-4 py-2 rounded-lg flex items-center gap-2 transition-all shadow-lg shadow-primary/20"
|
||
|
|
>
|
||
|
|
<Plus size={20} />
|
||
|
|
Add Printer
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{isAdding && (
|
||
|
|
<div className="bg-card border border-border rounded-xl p-6 shadow-xl animate-in fade-in slide-in-from-top-4">
|
||
|
|
<h3 className="text-lg font-semibold mb-4">New Printer</h3>
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium mb-1">Name</label>
|
||
|
|
<input
|
||
|
|
className="w-full bg-background border border-border rounded-md px-3 py-2"
|
||
|
|
placeholder="e.g. Ender 3 V2"
|
||
|
|
value={newPrinter.name}
|
||
|
|
onChange={e => setNewPrinter({ ...newPrinter, name: e.target.value })}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium mb-1">Power Consumption (Watts)</label>
|
||
|
|
<input
|
||
|
|
type="number"
|
||
|
|
className="w-full bg-background border border-border rounded-md px-3 py-2"
|
||
|
|
value={newPrinter.powerWatts}
|
||
|
|
onChange={e => setNewPrinter({ ...newPrinter, powerWatts: Number(e.target.value) })}
|
||
|
|
/>
|
||
|
|
<p className="text-xs text-muted-foreground mt-1">Found on printer label (avg 350W)</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium mb-1">Depreciation (R$/hr)</label>
|
||
|
|
<input
|
||
|
|
type="number"
|
||
|
|
className="w-full bg-background border border-border rounded-md px-3 py-2"
|
||
|
|
value={newPrinter.depreciationPerHour}
|
||
|
|
onChange={e => setNewPrinter({ ...newPrinter, depreciationPerHour: Number(e.target.value) })}
|
||
|
|
/>
|
||
|
|
<p className="text-xs text-muted-foreground mt-1">Optional machine wear cost</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="flex justify-end mt-4 gap-2">
|
||
|
|
<button
|
||
|
|
onClick={() => setIsAdding(false)}
|
||
|
|
className="px-4 py-2 text-muted-foreground hover:text-foreground"
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={handleSave}
|
||
|
|
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<Save size={18} />
|
||
|
|
Save Printer
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{loading ? (
|
||
|
|
<div className="text-center py-10 text-muted-foreground">Loading...</div>
|
||
|
|
) : (
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
|
|
{printers.map(printer => (
|
||
|
|
<div key={printer.id} className="bg-card border border-border rounded-xl p-6 shadow-sm hover:shadow-md transition-all relative group">
|
||
|
|
<div className="absolute top-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity">
|
||
|
|
<button
|
||
|
|
onClick={() => handleDelete(printer.id)}
|
||
|
|
className="text-red-500 hover:text-red-400 p-1 bg-background/80 rounded-full"
|
||
|
|
>
|
||
|
|
<Trash2 size={18} />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex items-center gap-4 mb-4">
|
||
|
|
<div className="bg-blue-500/10 p-3 rounded-full text-blue-500">
|
||
|
|
<PrinterIcon size={28} />
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 className="text-lg font-bold">{printer.name}</h3>
|
||
|
|
<div className="flex items-center gap-1 text-sm text-yellow-500">
|
||
|
|
<Zap size={14} fill="currentColor" />
|
||
|
|
<span>{printer.powerWatts}W</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-muted/50 rounded-lg p-3 space-y-2 text-sm">
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-muted-foreground">Consumption:</span>
|
||
|
|
<span className="font-mono">{(printer.powerWatts / 1000).toFixed(2)} kWh</span>
|
||
|
|
</div>
|
||
|
|
{printer.depreciationPerHour > 0 && (
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-muted-foreground">Depreciation:</span>
|
||
|
|
<span className="font-mono">R$ {printer.depreciationPerHour.toFixed(2)}/h</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default Printers;
|