Neuste Version der Seite mit Drag n Drop
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, Save, Wrench, User, Calendar, Tag, Hash, Building2 } from 'lucide-react';
|
||||
import { InstalledPart, Customer } from '../types';
|
||||
|
||||
interface InstalledPartModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (part: Omit<InstalledPart, 'id'> | InstalledPart) => Promise<void>;
|
||||
customers: Customer[];
|
||||
initialPart?: InstalledPart | null;
|
||||
defaultCustomerId?: string;
|
||||
}
|
||||
|
||||
export const InstalledPartModal: React.FC<InstalledPartModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
customers,
|
||||
initialPart,
|
||||
defaultCustomerId
|
||||
}) => {
|
||||
const [formData, setFormData] = useState({
|
||||
customerId: '',
|
||||
name: '',
|
||||
category: 'Gas-Brennwertgerät',
|
||||
manufacturer: 'Buderus',
|
||||
serialNumber: '',
|
||||
quantity: 1,
|
||||
installedAt: new Date().toISOString().split('T')[0]
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (initialPart) {
|
||||
setFormData({
|
||||
customerId: initialPart.customerId || '',
|
||||
name: initialPart.name || '',
|
||||
category: initialPart.category || 'Gas-Brennwertgerät',
|
||||
manufacturer: initialPart.manufacturer || 'Buderus',
|
||||
serialNumber: initialPart.serialNumber || '',
|
||||
quantity: initialPart.quantity || 1,
|
||||
installedAt: initialPart.installedAt || new Date().toISOString().split('T')[0]
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
customerId: defaultCustomerId || (customers.length > 0 ? customers[0].id : ''),
|
||||
name: '',
|
||||
category: 'Gas-Brennwertgerät',
|
||||
manufacturer: 'Buderus',
|
||||
serialNumber: '',
|
||||
quantity: 1,
|
||||
installedAt: new Date().toISOString().split('T')[0]
|
||||
});
|
||||
}
|
||||
setErrorMsg('');
|
||||
}, [initialPart, defaultCustomerId, customers, isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!formData.customerId) {
|
||||
setErrorMsg('Bitte einen Kunden auswählen.');
|
||||
return;
|
||||
}
|
||||
if (!formData.name.trim()) {
|
||||
setErrorMsg('Bitte Produktbezeichnung angeben.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
setErrorMsg('');
|
||||
if (initialPart) {
|
||||
await onSave({ ...initialPart, ...formData });
|
||||
} else {
|
||||
await onSave(formData);
|
||||
}
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
setErrorMsg(err.message || 'Fehler beim Speichern des Geräts');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
backgroundColor: 'rgba(15, 23, 42, 0.6)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
zIndex: 100,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '20px'
|
||||
}}>
|
||||
<div style={{
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '16px',
|
||||
maxWidth: '550px',
|
||||
width: '100%',
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto',
|
||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||
border: '1px solid #e2e8f0'
|
||||
}}>
|
||||
{/* Header */}
|
||||
<div style={{
|
||||
padding: '20px 24px',
|
||||
borderBottom: '1px solid #e2e8f0',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f8fafc',
|
||||
borderTopLeftRadius: '16px',
|
||||
borderTopRightRadius: '16px'
|
||||
}}>
|
||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 700, color: '#0f172a', margin: 0, display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<Wrench size={22} color="#2563eb" />
|
||||
{initialPart ? 'Verbautes Gerät bearbeiten' : 'Gerät zur Kartei hinzufügen'}
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#64748b', borderRadius: '8px', padding: '4px' }}
|
||||
>
|
||||
<X size={20} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Form Body */}
|
||||
<form onSubmit={handleSubmit} style={{ padding: '24px' }}>
|
||||
{errorMsg && (
|
||||
<div style={{
|
||||
backgroundColor: '#fef2f2',
|
||||
color: '#991b1b',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '20px',
|
||||
fontSize: '0.9rem',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
{errorMsg}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Customer */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Zugehöriger Kunde *
|
||||
</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<User size={18} color="#94a3b8" style={{ position: 'absolute', left: '12px', top: '12px' }} />
|
||||
<select
|
||||
value={formData.customerId}
|
||||
onChange={(e) => setFormData({ ...formData, customerId: e.target.value })}
|
||||
required
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px 10px 38px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem',
|
||||
backgroundColor: '#ffffff'
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>-- Bitte Kunde auswählen --</option>
|
||||
{customers.map((c) => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{c.name} ({c.customerNumber})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Name */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Produktbezeichnung / Modell *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder="z.B. Logamax plus GB172-24"
|
||||
required
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Manufacturer & Category */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Hersteller / Marke *
|
||||
</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Building2 size={18} color="#94a3b8" style={{ position: 'absolute', left: '12px', top: '12px' }} />
|
||||
<input
|
||||
type="text"
|
||||
value={formData.manufacturer}
|
||||
onChange={(e) => setFormData({ ...formData, manufacturer: e.target.value })}
|
||||
placeholder="z.B. Buderus, Bosch, Viessmann"
|
||||
required
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px 10px 38px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Geräte-Typ / Kategorie
|
||||
</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Tag size={18} color="#94a3b8" style={{ position: 'absolute', left: '12px', top: '12px' }} />
|
||||
<input
|
||||
type="text"
|
||||
value={formData.category}
|
||||
onChange={(e) => setFormData({ ...formData, category: e.target.value })}
|
||||
placeholder="z.B. Gas-Brennwertgerät, Raumthermostat"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px 10px 38px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Serial Number & Quantity */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Seriennummer
|
||||
</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Hash size={18} color="#94a3b8" style={{ position: 'absolute', left: '12px', top: '12px' }} />
|
||||
<input
|
||||
type="text"
|
||||
value={formData.serialNumber}
|
||||
onChange={(e) => setFormData({ ...formData, serialNumber: e.target.value })}
|
||||
placeholder="8374747383"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px 10px 38px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Anzahl
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={formData.quantity}
|
||||
onChange={(e) => setFormData({ ...formData, quantity: parseInt(e.target.value) || 1 })}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Installed At */}
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, color: '#475569', marginBottom: '6px' }}>
|
||||
Einbaudatum
|
||||
</label>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Calendar size={18} color="#94a3b8" style={{ position: 'absolute', left: '12px', top: '12px' }} />
|
||||
<input
|
||||
type="date"
|
||||
value={formData.installedAt}
|
||||
onChange={(e) => setFormData({ ...formData, installedAt: e.target.value })}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px 14px 10px 38px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
fontSize: '0.95rem'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', paddingTop: '16px', borderTop: '1px solid #f1f5f9' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
style={{
|
||||
padding: '10px 18px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #cbd5e1',
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#475569',
|
||||
fontWeight: 600,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
backgroundColor: '#2563eb',
|
||||
color: '#ffffff',
|
||||
fontWeight: 600,
|
||||
cursor: isSaving ? 'not-allowed' : 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
opacity: isSaving ? 0.7 : 1
|
||||
}}
|
||||
>
|
||||
<Save size={18} />
|
||||
{isSaving ? 'Speichert...' : 'Gerät speichern'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user