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:
90
src/admin/components/OfferCustomerPicker.jsx
Normal file
90
src/admin/components/OfferCustomerPicker.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user