refactor: P3 dekompozice velkych komponent

Dashboard.jsx (1346 -> 378 LOC):
- DashKpiCards, DashQuickActions, DashActivityFeed, DashAttendanceToday, DashProfile, DashSessions
- dashboardHelpers.js (konstanty + helper funkce)

OfferDetail.jsx (1061 -> ~530 LOC):
- useOfferForm hook (form state, draft, items/sections, submit)
- OfferCustomerPicker (customer search/select dropdown)

AttendanceAdmin.jsx (1036 -> ~275 LOC):
- useAttendanceAdmin hook (data fetching, filters, CRUD, print)
- AttendanceShiftTable (shift records table)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 18:22:38 +01:00
parent 6863c7c557
commit df506dfea4
14 changed files with 2558 additions and 2297 deletions

View File

@@ -0,0 +1,90 @@
import { useState, useEffect, useMemo } from 'react'
export default function OfferCustomerPicker({
customers,
customerId,
customerName,
onSelect,
onClear,
error,
readOnly
}) {
const [customerSearch, setCustomerSearch] = useState('')
const [showDropdown, setShowDropdown] = useState(false)
// Close dropdown on outside click
useEffect(() => {
const handleClickOutside = () => setShowDropdown(false)
if (showDropdown) {
document.addEventListener('click', handleClickOutside)
return () => document.removeEventListener('click', handleClickOutside)
}
}, [showDropdown])
const filteredCustomers = useMemo(() => {
if (!customerSearch) return customers
const q = customerSearch.toLowerCase()
return customers.filter(c =>
(c.name || '').toLowerCase().includes(q) ||
(c.company_id || '').includes(customerSearch) ||
(c.city || '').toLowerCase().includes(q)
)
}, [customers, customerSearch])
const handleSelect = (customer) => {
onSelect(customer)
setCustomerSearch('')
setShowDropdown(false)
}
return (
<div className={`admin-form-group${error ? ' has-error' : ''}`}>
<label className="admin-form-label required">Zákazník</label>
{customerId && (
<div className="offers-customer-selected">
<span>{customerName}</span>
{!readOnly && (
<button type="button" onClick={onClear} className="admin-btn-icon" title="Odebrat zákazníka" aria-label="Odebrat zákazníka">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
)}
</div>
)}
{!customerId && !readOnly && (
<div className="offers-customer-select" onClick={(e) => e.stopPropagation()}>
<input
type="text"
value={customerSearch}
onChange={(e) => { setCustomerSearch(e.target.value); setShowDropdown(true) }}
onFocus={() => setShowDropdown(true)}
className="admin-form-input"
placeholder="Hledat zákazníka..."
/>
{showDropdown && (
<div className="offers-customer-dropdown">
{filteredCustomers.length === 0 ? (
<div className="offers-customer-dropdown-empty">
Žádní zákazníci
</div>
) : (
filteredCustomers.slice(0, 10).map(c => (
<div
key={c.id}
className="offers-customer-dropdown-item"
onMouseDown={() => handleSelect(c)}
>
<div>{c.name}</div>
{c.city && <div>{c.city}</div>}
</div>
))
)}
</div>
)}
</div>
)}
{error && <span className="admin-form-error">{error}</span>}
</div>
)
}