feat: Add edit button and modal for battery groups

Users can now edit battery group counts and notes via an edit button on
each battery card. The edit modal allows updating available count,
charging count, and notes.

Changes:
- Create EditBatteryModal component
- Add edit button (pencil icon) to BatteryCard
- Uses existing PATCH /api/batteries/[id] endpoint

🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
root 2026-01-19 17:17:31 +00:00
parent febdc8beab
commit 9d59024fed
2 changed files with 155 additions and 1 deletions

View File

@ -9,7 +9,8 @@ import { Input } from '@/components/ui/Input';
import { Select } from '@/components/ui/Select'; import { Select } from '@/components/ui/Select';
import { useToast } from '@/components/ui/Toast'; import { useToast } from '@/components/ui/Toast';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Battery, BatteryCharging, Monitor, Trash2, ArrowRight } from 'lucide-react'; import { Battery, BatteryCharging, Monitor, Trash2, ArrowRight, Pencil } from 'lucide-react';
import { EditBatteryModal } from './EditBatteryModal';
interface Device { interface Device {
id: number; id: number;
@ -36,6 +37,7 @@ export function BatteryCard({ battery, devices }: BatteryCardProps) {
const router = useRouter(); const router = useRouter();
const { showToast } = useToast(); const { showToast } = useToast();
const [actionModal, setActionModal] = useState<'charge' | 'available' | 'assign' | 'delete' | null>(null); const [actionModal, setActionModal] = useState<'charge' | 'available' | 'assign' | 'delete' | null>(null);
const [showEditModal, setShowEditModal] = useState(false);
const [count, setCount] = useState('1'); const [count, setCount] = useState('1');
const [deviceId, setDeviceId] = useState(''); const [deviceId, setDeviceId] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -148,6 +150,14 @@ export function BatteryCard({ battery, devices }: BatteryCardProps) {
</h3> </h3>
<p className="text-sm text-slate-500">Total: {total} batteries</p> <p className="text-sm text-slate-500">Total: {total} batteries</p>
</div> </div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowEditModal(true)}
className="text-slate-400 hover:text-slate-600"
>
<Pencil className="w-4 h-4" />
</Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@ -356,6 +366,13 @@ export function BatteryCard({ battery, devices }: BatteryCardProps) {
</p> </p>
)} )}
</Modal> </Modal>
{/* Edit Modal */}
<EditBatteryModal
isOpen={showEditModal}
onClose={() => setShowEditModal(false)}
battery={battery}
/>
</> </>
); );
} }

View File

@ -0,0 +1,137 @@
'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Modal } from '@/components/ui/Modal';
import { useToast } from '@/components/ui/Toast';
import { useRouter } from 'next/navigation';
interface BatteryGroup {
id: number;
brandName: string;
typeName: string;
chemistryName: string;
availableCount: number;
chargingCount: number;
inUseCount: number;
notes: string | null;
}
interface EditBatteryModalProps {
isOpen: boolean;
onClose: () => void;
battery: BatteryGroup | null;
}
export function EditBatteryModal({ isOpen, onClose, battery }: EditBatteryModalProps) {
const router = useRouter();
const { showToast } = useToast();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
availableCount: '0',
chargingCount: '0',
notes: '',
});
useEffect(() => {
if (battery) {
setFormData({
availableCount: battery.availableCount.toString(),
chargingCount: battery.chargingCount.toString(),
notes: battery.notes || '',
});
}
}, [battery]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!battery) return;
setLoading(true);
try {
const res = await fetch(`/api/batteries/${battery.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
availableCount: parseInt(formData.availableCount) || 0,
chargingCount: parseInt(formData.chargingCount) || 0,
notes: formData.notes || null,
}),
});
if (!res.ok) {
const error = await res.json();
throw new Error(error.error || 'Failed to update battery');
}
showToast('success', 'Battery group updated');
onClose();
router.refresh();
} catch (error) {
showToast('error', error instanceof Error ? error.message : 'Failed to update battery');
} finally {
setLoading(false);
}
};
if (!battery) return null;
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title="Edit Battery Group"
footer={
<div className="flex justify-end gap-3">
<Button variant="secondary" onClick={onClose} disabled={loading}>
Cancel
</Button>
<Button onClick={handleSubmit} disabled={loading}>
{loading ? 'Saving...' : 'Save Changes'}
</Button>
</div>
}
>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="bg-slate-50 rounded-lg p-3 mb-4">
<p className="text-sm text-slate-600">
<span className="font-medium text-slate-900">
{battery.brandName} {battery.typeName} ({battery.chemistryName})
</span>
</p>
{battery.inUseCount > 0 && (
<p className="text-xs text-slate-500 mt-1">
{battery.inUseCount} currently in use (managed via device assignments)
</p>
)}
</div>
<div className="grid grid-cols-2 gap-4">
<Input
label="Available Count"
type="number"
min="0"
value={formData.availableCount}
onChange={(e) => setFormData({ ...formData, availableCount: e.target.value })}
/>
<Input
label="Charging Count"
type="number"
min="0"
value={formData.chargingCount}
onChange={(e) => setFormData({ ...formData, chargingCount: e.target.value })}
/>
</div>
<Input
label="Notes (optional)"
value={formData.notes}
onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
placeholder="Any additional notes..."
/>
</form>
</Modal>
);
}