feat: slot click opens edit, status buttons for all states, manual entry without email
Build & Push Docker Image / build (push) Successful in 14s
Build & Push Docker Image / build (push) Successful in 14s
This commit is contained in:
@@ -37,8 +37,8 @@ bookingsRouter.get('/', (req, res) => {
|
||||
// IT/AM manually create booking for a customer
|
||||
bookingsRouter.post('/manual', (req, res) => {
|
||||
const { slot_id, customer_email, customer_name, customer_company, customer_location, cc_email, notes } = req.body;
|
||||
if (!slot_id || !customer_email || !customer_name) {
|
||||
return res.status(400).json({ error: 'slot_id, customer_email, customer_name required' });
|
||||
if (!slot_id || !customer_name) {
|
||||
return res.status(400).json({ error: 'slot_id and customer_name required' });
|
||||
}
|
||||
const db = getDb();
|
||||
const slot = db.prepare('SELECT * FROM slots WHERE id = ?').get(slot_id) as any;
|
||||
|
||||
@@ -199,7 +199,7 @@ export function SlotsPage() {
|
||||
blockedDates={blockedDaysInWeek}
|
||||
onPrevWeek={() => setWeekStart(prev => new Date(prev.getTime() - 7 * 86400000))}
|
||||
onNextWeek={() => setWeekStart(prev => new Date(prev.getTime() + 7 * 86400000))}
|
||||
onSlotClick={(slot) => setSelectedSlot(slot)}
|
||||
onSlotClick={(slot) => { setSelectedSlot(slot); openEdit(slot); }}
|
||||
onCellClick={(date, start_time) => openCreate(date, start_time)}
|
||||
dayCapacities={dailyCapacities}
|
||||
onCapacityChange={(date, delta) => {
|
||||
@@ -229,9 +229,8 @@ export function SlotsPage() {
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-xs text-slate-500 dark:text-white/40">{selectedSlot.total_booked}/{selectedSlot.max_bookings} belegt</span>
|
||||
<span className="text-xs text-slate-500 dark:text-white/40">{selectedSlot.total_booked} Buchungen</span>
|
||||
{selectedSlot.blocked_count > 0 && <span className="text-xs font-medium text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-500/10 px-2 py-0.5 rounded border border-red-200 dark:border-red-500/20">{selectedSlot.blocked_count} blockiert</span>}
|
||||
<span className="text-xs text-slate-400 dark:text-white/30">· {selectedSlot.max_bookings - (selectedSlot.blocked_count || 0) - selectedSlot.total_booked} frei</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 mb-4">
|
||||
@@ -287,6 +286,16 @@ export function SlotsPage() {
|
||||
<Button size="sm" variant="danger" onClick={() => cancelMutation.mutate(b.id)}>Stornieren</Button>
|
||||
</div>
|
||||
)}
|
||||
{b.status === 'confirmed' && (
|
||||
<div className="flex gap-1.5 mt-2 pt-2 border-t border-slate-200 dark:border-white/[0.06]">
|
||||
<Button size="sm" variant="danger" onClick={() => cancelMutation.mutate(b.id)}>Stornieren</Button>
|
||||
</div>
|
||||
)}
|
||||
{b.status === 'cancelled' && (
|
||||
<div className="flex gap-1.5 mt-2 pt-2 border-t border-slate-200 dark:border-white/[0.06]">
|
||||
<Button size="sm" onClick={() => confirmMutation.mutate(b.id)}>Reaktivieren</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -294,16 +303,16 @@ export function SlotsPage() {
|
||||
{manualForm ? (
|
||||
<div className="bg-slate-50 dark:bg-white/[0.04] rounded-lg p-3 border border-slate-200 dark:border-white/[0.06] space-y-2">
|
||||
<p className="text-xs font-medium text-slate-700 dark:text-white/70">Kunde manuell eintragen</p>
|
||||
<Input placeholder="Name *" value={manualName} onChange={e => setManualName(e.target.value)} />
|
||||
<Input placeholder="Email *" type="email" value={manualEmail} onChange={e => setManualEmail(e.target.value)} />
|
||||
<Input placeholder="Name" value={manualName} onChange={e => setManualName(e.target.value)} />
|
||||
<Input placeholder="Email (optional)" type="email" value={manualEmail} onChange={e => setManualEmail(e.target.value)} />
|
||||
<Input placeholder="Unternehmen" value={manualCompany} onChange={e => setManualCompany(e.target.value)} />
|
||||
<Input placeholder="Standort" value={manualLocation} onChange={e => setManualLocation(e.target.value)} />
|
||||
<Input placeholder="CC-Email (IT)" type="email" value={manualCC} onChange={e => setManualCC(e.target.value)} />
|
||||
<textarea placeholder="Anmerkungen (optional)" value={manualNotes} onChange={e => setManualNotes(e.target.value)} rows={2}
|
||||
className="w-full px-2 py-1.5 text-xs bg-white dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-lg text-slate-900 dark:text-white/90 placeholder:text-slate-400 dark:placeholder:text-white/25" />
|
||||
<div className="flex gap-1.5">
|
||||
<Button size="sm" disabled={!manualName || !manualEmail || manualBookingMutation.isPending} onClick={() => manualBookingMutation.mutate({
|
||||
slot_id: selectedSlot.id, customer_name: manualName, customer_email: manualEmail, customer_company: manualCompany, customer_location: manualLocation, cc_email: manualCC, notes: manualNotes,
|
||||
<Button size="sm" disabled={!manualName || manualBookingMutation.isPending} onClick={() => manualBookingMutation.mutate({
|
||||
slot_id: selectedSlot.id, customer_name: manualName, customer_email: manualEmail || null, customer_company: manualCompany, customer_location: manualLocation, cc_email: manualCC, notes: manualNotes,
|
||||
})}>Eintragen</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => setManualForm(false)}>Abbrechen</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user