feat(a11y): Implement ARIA accessibility sweep and keyboard navigation
- Added visually hidden labels, IDs, and names to form inputs across all key components and pages (modals, directories, chatbots) - Added tabIndex and onKeyDown handlers to clickable table rows in OwnerProjectDetail for keyboard accessibility - Validated existing ARIA roles and Escape key bindings on modal components
This commit is contained in:
@@ -716,7 +716,10 @@ const Chatbot = (props) => {
|
|||||||
{/* Input */}
|
{/* Input */}
|
||||||
<div className="p-4 bg-white dark:bg-zinc-900 border-t border-gray-100 dark:border-zinc-800">
|
<div className="p-4 bg-white dark:bg-zinc-900 border-t border-gray-100 dark:border-zinc-800">
|
||||||
<div className="flex items-center space-x-2 bg-slate-100 dark:bg-zinc-800 rounded-full px-4 py-3">
|
<div className="flex items-center space-x-2 bg-slate-100 dark:bg-zinc-800 rounded-full px-4 py-3">
|
||||||
|
<label htmlFor="chat-input-inline" className="sr-only">Message</label>
|
||||||
<input
|
<input
|
||||||
|
id="chat-input-inline"
|
||||||
|
name="chat-input"
|
||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
@@ -817,7 +820,10 @@ const Chatbot = (props) => {
|
|||||||
{/* Input */}
|
{/* Input */}
|
||||||
<div className="p-3 bg-white dark:bg-zinc-900 border-t border-gray-100 dark:border-zinc-800">
|
<div className="p-3 bg-white dark:bg-zinc-900 border-t border-gray-100 dark:border-zinc-800">
|
||||||
<div className="flex items-center space-x-2 bg-slate-100 dark:bg-zinc-800 rounded-full px-4 py-2">
|
<div className="flex items-center space-x-2 bg-slate-100 dark:bg-zinc-800 rounded-full px-4 py-2">
|
||||||
|
<label htmlFor="chat-input-widget" className="sr-only">Message</label>
|
||||||
<input
|
<input
|
||||||
|
id="chat-input-widget"
|
||||||
|
name="chat-input"
|
||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
|||||||
@@ -124,8 +124,11 @@ const FinancialSummaryModal = ({ isOpen, onClose, role, data }) => {
|
|||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="px-4 sm:px-6 py-3 sm:py-4 border-b border-zinc-200 dark:border-white/5 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-between bg-white dark:bg-[#121214]">
|
<div className="px-4 sm:px-6 py-3 sm:py-4 border-b border-zinc-200 dark:border-white/5 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-between bg-white dark:bg-[#121214]">
|
||||||
<div className="relative w-full sm:w-96">
|
<div className="relative w-full sm:w-96">
|
||||||
|
<label htmlFor="search-contractor-financial" className="sr-only">Search transactions</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-contractor-financial"
|
||||||
|
name="search"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search transactions..."
|
placeholder="Search transactions..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
@@ -156,7 +159,7 @@ const FinancialSummaryModal = ({ isOpen, onClose, role, data }) => {
|
|||||||
<span className={`px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wide ${item.status === 'paid' || item.status === 'completed'
|
<span className={`px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wide ${item.status === 'paid' || item.status === 'completed'
|
||||||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
||||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
||||||
}`}>{item.status}</span>
|
}`}>{item.status}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -199,8 +202,8 @@ const FinancialSummaryModal = ({ isOpen, onClose, role, data }) => {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wide whitespace-nowrap ${item.status === 'paid' || item.status === 'completed'
|
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wide whitespace-nowrap ${item.status === 'paid' || item.status === 'completed'
|
||||||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
||||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
||||||
}`}>
|
}`}>
|
||||||
{item.status}
|
{item.status}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ export const AgentSelectionModal = ({ isOpen, onClose, onSelect }) => {
|
|||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="p-4 border-b border-zinc-100 dark:border-zinc-800 flex-shrink-0">
|
<div className="p-4 border-b border-zinc-100 dark:border-zinc-800 flex-shrink-0">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
<label htmlFor="search-agents" className="sr-only">Search agents</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-agents"
|
||||||
|
name="search-agents"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search agents..."
|
placeholder="Search agents..."
|
||||||
className="w-full pl-9 pr-4 py-2 bg-zinc-100 dark:bg-black/20 border border-transparent focus:border-blue-500 rounded-xl text-sm outline-none transition-all dark:text-white"
|
className="w-full pl-9 pr-4 py-2 bg-zinc-100 dark:bg-black/20 border border-transparent focus:border-blue-500 rounded-xl text-sm outline-none transition-all dark:text-white"
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ const StatusBadge = ({ status }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const InputGroup = ({ label, children }) => (
|
const InputGroup = ({ label, children, htmlFor }) => (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[10px] uppercase font-bold text-zinc-400 tracking-widest ml-0.5">{label}</label>
|
<label htmlFor={htmlFor} className="text-[10px] uppercase font-bold text-zinc-400 tracking-widest ml-0.5">{label}</label>
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -256,6 +256,8 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
|
|||||||
|
|
||||||
// Helper for Inputs
|
// Helper for Inputs
|
||||||
const RenderInput = ({ label, field, type = "text", placeholder, options, locked = false }) => {
|
const RenderInput = ({ label, field, type = "text", placeholder, options, locked = false }) => {
|
||||||
|
const inputId = `prop-${field}`;
|
||||||
|
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
return (
|
return (
|
||||||
<InputGroup label={label}>
|
<InputGroup label={label}>
|
||||||
@@ -277,9 +279,11 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputGroup label={label}>
|
<InputGroup label={label} htmlFor={inputId}>
|
||||||
{options ? (
|
{options ? (
|
||||||
<select
|
<select
|
||||||
|
id={inputId}
|
||||||
|
name={field}
|
||||||
value={formData[field]}
|
value={formData[field]}
|
||||||
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
|
||||||
className="w-full bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/10 rounded-lg py-2 px-2 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 appearance-none"
|
className="w-full bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/10 rounded-lg py-2 px-2 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 appearance-none"
|
||||||
@@ -292,6 +296,8 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
|
|||||||
</select>
|
</select>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
|
id={inputId}
|
||||||
|
name={field}
|
||||||
type={type}
|
type={type}
|
||||||
value={formData[field]}
|
value={formData[field]}
|
||||||
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
|
||||||
@@ -374,10 +380,10 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
|
|||||||
{/* Status Selector */}
|
{/* Status Selector */}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-[10px] uppercase font-bold text-zinc-500 tracking-widest ml-1">Canvassing Status</label>
|
<span id="status-label" className="block text-[10px] uppercase font-bold text-zinc-500 tracking-widest ml-1">Canvassing Status</span>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
<div role="group" aria-labelledby="status-label" className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
||||||
{["Neutral", "Hot Lead", "Renovated", "Customer", "Not Interested"].map(s => (
|
{["Neutral", "Hot Lead", "Renovated", "Customer", "Not Interested"].map(s => (
|
||||||
<button key={s} onClick={() => setFormData({ ...formData, status: s })} className={`px-1 py-2 rounded text-[10px] font-bold border transition-all ${formData.status === s ? 'bg-zinc-900 dark:bg-white text-white dark:text-black' : 'bg-transparent border-zinc-200 dark:border-white/10 text-zinc-500'}`}>{s}</button>
|
<button key={s} aria-pressed={formData.status === s} onClick={() => setFormData({ ...formData, status: s })} className={`px-1 py-2 rounded text-[10px] font-bold border transition-all ${formData.status === s ? 'bg-zinc-900 dark:bg-white text-white dark:text-black' : 'bg-transparent border-zinc-200 dark:border-white/10 text-zinc-500'}`}>{s}</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -551,10 +557,12 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
|
|||||||
{/* SECTION 6: NOTES */}
|
{/* SECTION 6: NOTES */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-zinc-500">
|
<div className="flex items-center gap-2 pb-2 border-b border-zinc-200 dark:border-white/5 text-zinc-500">
|
||||||
<Edit2 size={16} /> <h3 className="text-xs font-black uppercase tracking-widest">Field Notes</h3>
|
<Edit2 size={16} /> <h3 className="text-xs font-black uppercase tracking-widest"><label htmlFor="prop-notes">Field Notes</label></h3>
|
||||||
</div>
|
</div>
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<textarea
|
<textarea
|
||||||
|
id="prop-notes"
|
||||||
|
name="notes"
|
||||||
className="w-full h-32 bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/10 rounded-xl p-3 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 resize-none"
|
className="w-full h-32 bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/10 rounded-xl p-3 text-sm text-zinc-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500 resize-none"
|
||||||
value={formData.notes}
|
value={formData.notes}
|
||||||
placeholder="Add notes..."
|
placeholder="Add notes..."
|
||||||
@@ -615,6 +623,8 @@ const PropertyDetailDrawer = ({ isOpen, onClose, data, newLocation, onSave, load
|
|||||||
<span className="text-xl">+</span>
|
<span className="text-xl">+</span>
|
||||||
<span className="text-xs uppercase tracking-widest">Upload Photo</span>
|
<span className="text-xs uppercase tracking-widest">Upload Photo</span>
|
||||||
<input
|
<input
|
||||||
|
id="photo-upload"
|
||||||
|
name="photo-upload"
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ const CreateItemModal = ({ isOpen, onClose, title, icon: Icon, iconColor = "text
|
|||||||
<form onSubmit={handleSubmit} className="p-6 space-y-5">
|
<form onSubmit={handleSubmit} className="p-6 space-y-5">
|
||||||
{fields.map((field, i) => (
|
{fields.map((field, i) => (
|
||||||
<div key={i} className="space-y-1.5">
|
<div key={i} className="space-y-1.5">
|
||||||
<label className="text-xs font-bold uppercase tracking-wider text-zinc-400">{field.label}</label>
|
<label htmlFor={field.name} className="text-xs font-bold uppercase tracking-wider text-zinc-400">{field.label}</label>
|
||||||
{field.type === 'textarea' ? (
|
{field.type === 'textarea' ? (
|
||||||
<textarea
|
<textarea
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
className="w-full bg-[#18181b] border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-[#00f0ff] focus:ring-1 focus:ring-[#00f0ff] transition-all"
|
className="w-full bg-[#18181b] border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-[#00f0ff] focus:ring-1 focus:ring-[#00f0ff] transition-all"
|
||||||
rows={4}
|
rows={4}
|
||||||
placeholder={field.placeholder || ''}
|
placeholder={field.placeholder || ''}
|
||||||
@@ -55,6 +57,8 @@ const CreateItemModal = ({ isOpen, onClose, title, icon: Icon, iconColor = "text
|
|||||||
/>
|
/>
|
||||||
) : field.type === 'select' ? (
|
) : field.type === 'select' ? (
|
||||||
<select
|
<select
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
className="w-full bg-[#18181b] border border-white/10 rounded-xl px-4 py-3 text-sm text-white focus:outline-none focus:border-[#00f0ff] focus:ring-1 focus:ring-[#00f0ff] transition-all"
|
className="w-full bg-[#18181b] border border-white/10 rounded-xl px-4 py-3 text-sm text-white focus:outline-none focus:border-[#00f0ff] focus:ring-1 focus:ring-[#00f0ff] transition-all"
|
||||||
required={field.required}
|
required={field.required}
|
||||||
value={formData[field.name] || ''}
|
value={formData[field.name] || ''}
|
||||||
@@ -65,6 +69,8 @@ const CreateItemModal = ({ isOpen, onClose, title, icon: Icon, iconColor = "text
|
|||||||
</select>
|
</select>
|
||||||
) : field.type === 'file' ? (
|
) : field.type === 'file' ? (
|
||||||
<input
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
type="file"
|
type="file"
|
||||||
accept={field.accept}
|
accept={field.accept}
|
||||||
className="w-full text-sm text-zinc-400 file:mr-4 file:py-2.5 file:px-4 file:rounded-xl file:border file:border-white/10 file:text-sm file:font-bold file:bg-[#18181b] file:text-white hover:file:bg-white/5 transition-all"
|
className="w-full text-sm text-zinc-400 file:mr-4 file:py-2.5 file:px-4 file:rounded-xl file:border file:border-white/10 file:text-sm file:font-bold file:bg-[#18181b] file:text-white hover:file:bg-white/5 transition-all"
|
||||||
@@ -73,6 +79,8 @@ const CreateItemModal = ({ isOpen, onClose, title, icon: Icon, iconColor = "text
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
type={field.type || 'text'}
|
type={field.type || 'text'}
|
||||||
className="w-full bg-[#18181b] border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-[#00f0ff] focus:ring-1 focus:ring-[#00f0ff] transition-all"
|
className="w-full bg-[#18181b] border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder-zinc-600 focus:outline-none focus:border-[#00f0ff] focus:ring-1 focus:ring-[#00f0ff] transition-all"
|
||||||
placeholder={field.placeholder || ''}
|
placeholder={field.placeholder || ''}
|
||||||
|
|||||||
@@ -183,8 +183,11 @@ const FinancialDetailsModal = ({ isOpen, onClose, type, ownerId }) => {
|
|||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="px-4 sm:px-6 py-3 sm:py-4 border-b border-zinc-200 dark:border-white/5 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-between bg-white dark:bg-[#121214]">
|
<div className="px-4 sm:px-6 py-3 sm:py-4 border-b border-zinc-200 dark:border-white/5 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-between bg-white dark:bg-[#121214]">
|
||||||
<div className="relative w-full sm:w-96">
|
<div className="relative w-full sm:w-96">
|
||||||
|
<label htmlFor="search-owner-financial" className="sr-only">Search transactions</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-owner-financial"
|
||||||
|
name="search"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by name, ID..."
|
placeholder="Search by name, ID..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
|
|||||||
+8
-5
@@ -125,8 +125,11 @@ const VendorFinancialSummaryModal = ({ isOpen, onClose, data }) => {
|
|||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="px-4 sm:px-6 py-3 sm:py-4 border-b border-zinc-200 dark:border-white/5 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-between bg-white dark:bg-[#121214]">
|
<div className="px-4 sm:px-6 py-3 sm:py-4 border-b border-zinc-200 dark:border-white/5 flex flex-col sm:flex-row gap-3 sm:gap-4 justify-between bg-white dark:bg-[#121214]">
|
||||||
<div className="relative w-full sm:w-96">
|
<div className="relative w-full sm:w-96">
|
||||||
|
<label htmlFor="search-vendor-financial" className="sr-only">Search invoices</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-vendor-financial"
|
||||||
|
name="search"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search invoices..."
|
placeholder="Search invoices..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
@@ -158,7 +161,7 @@ const VendorFinancialSummaryModal = ({ isOpen, onClose, data }) => {
|
|||||||
: invoice.status === 'pending'
|
: invoice.status === 'pending'
|
||||||
? 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
? 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
||||||
: 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400'
|
: 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400'
|
||||||
}`}>{invoice.status}</span>
|
}`}>{invoice.status}</span>
|
||||||
<span className="text-xs text-zinc-500">Due: {invoice.dueDate}</span>
|
<span className="text-xs text-zinc-500">Due: {invoice.dueDate}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,10 +213,10 @@ const VendorFinancialSummaryModal = ({ isOpen, onClose, data }) => {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wide whitespace-nowrap ${invoice.status === 'paid'
|
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wide whitespace-nowrap ${invoice.status === 'paid'
|
||||||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
||||||
: invoice.status === 'pending'
|
: invoice.status === 'pending'
|
||||||
? 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
? 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
||||||
: 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400'
|
: 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400'
|
||||||
}`}>
|
}`}>
|
||||||
{invoice.status}
|
{invoice.status}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -230,14 +230,20 @@ const AdminSchedule = () => {
|
|||||||
</button>
|
</button>
|
||||||
<div className="border-t border-zinc-200 dark:border-zinc-700/50 p-3">
|
<div className="border-t border-zinc-200 dark:border-zinc-700/50 p-3">
|
||||||
<p className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-2">Custom Range</p>
|
<p className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-2">Custom Range</p>
|
||||||
|
<label htmlFor="custom-date-start" className="sr-only">Start Date</label>
|
||||||
<input
|
<input
|
||||||
|
id="custom-date-start"
|
||||||
|
name="custom-date-start"
|
||||||
type="date"
|
type="date"
|
||||||
value={customDateRange.start}
|
value={customDateRange.start}
|
||||||
onChange={(e) => setCustomDateRange(prev => ({ ...prev, start: e.target.value }))}
|
onChange={(e) => setCustomDateRange(prev => ({ ...prev, start: e.target.value }))}
|
||||||
className="w-full px-2 py-1 text-xs bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded mb-2 text-zinc-900 dark:text-white"
|
className="w-full px-2 py-1 text-xs bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded mb-2 text-zinc-900 dark:text-white"
|
||||||
placeholder="Start date"
|
placeholder="Start date"
|
||||||
/>
|
/>
|
||||||
|
<label htmlFor="custom-date-end" className="sr-only">End Date</label>
|
||||||
<input
|
<input
|
||||||
|
id="custom-date-end"
|
||||||
|
name="custom-date-end"
|
||||||
type="date"
|
type="date"
|
||||||
value={customDateRange.end}
|
value={customDateRange.end}
|
||||||
onChange={(e) => setCustomDateRange(prev => ({ ...prev, end: e.target.value }))}
|
onChange={(e) => setCustomDateRange(prev => ({ ...prev, end: e.target.value }))}
|
||||||
@@ -272,9 +278,12 @@ const AdminSchedule = () => {
|
|||||||
{availableStatuses.map(status => (
|
{availableStatuses.map(status => (
|
||||||
<label
|
<label
|
||||||
key={status}
|
key={status}
|
||||||
|
htmlFor={`status-filter-${status.replace(/\s+/g, '-')}`}
|
||||||
className="flex items-center gap-2 px-4 py-2 text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors"
|
className="flex items-center gap-2 px-4 py-2 text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800 cursor-pointer transition-colors"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
id={`status-filter-${status.replace(/\s+/g, '-')}`}
|
||||||
|
name={`status-filter-${status.replace(/\s+/g, '-')}`}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={statusFilter.includes(status)}
|
checked={statusFilter.includes(status)}
|
||||||
onChange={() => toggleStatusFilter(status)}
|
onChange={() => toggleStatusFilter(status)}
|
||||||
|
|||||||
@@ -129,12 +129,14 @@ const CustomerProfile = () => {
|
|||||||
{activeTab === 'details' ? (
|
{activeTab === 'details' ? (
|
||||||
<SpotlightCard>
|
<SpotlightCard>
|
||||||
<form onSubmit={handleSaveProfile} className="p-8 space-y-6">
|
<form onSubmit={handleSaveProfile} className="p-8 space-y-6">
|
||||||
<FormInput label="Full Name" icon={User} value={formData.name} onChange={v => setFormData({ ...formData, name: v })} required />
|
<FormInput id="customer-name" name="name" label="Full Name" icon={User} value={formData.name} onChange={v => setFormData({ ...formData, name: v })} required />
|
||||||
<FormInput label="Email Address" icon={null} prefix="@" type="email" value={formData.email} onChange={v => setFormData({ ...formData, email: v })} required />
|
<FormInput id="customer-email" name="email" label="Email Address" icon={null} prefix="@" type="email" value={formData.email} onChange={v => setFormData({ ...formData, email: v })} required />
|
||||||
<FormInput label="Phone Number" value={formData.phone} onChange={v => setFormData({ ...formData, phone: v })} required />
|
<FormInput id="customer-phone" name="phone" label="Phone Number" value={formData.phone} onChange={v => setFormData({ ...formData, phone: v })} required />
|
||||||
<div className="space-y-2 md:col-span-2">
|
<div className="space-y-2 md:col-span-2">
|
||||||
<label className="text-xs font-bold uppercase text-zinc-500">Address</label>
|
<label htmlFor="customer-address" className="text-xs font-bold uppercase text-zinc-500">Address</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
id="customer-address"
|
||||||
|
name="address"
|
||||||
value={formData.address}
|
value={formData.address}
|
||||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||||
className="w-full px-4 py-2 bg-zinc-100 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all h-24 resize-none"
|
className="w-full px-4 py-2 bg-zinc-100 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all h-24 resize-none"
|
||||||
@@ -168,16 +170,16 @@ const CustomerProfile = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<FormInput label="Address" value="2612 Dunwick Dr, Plano, TX 75023" onChange={() => { }} />
|
<FormInput id="prop-address" name="prop-address" label="Address" value="2612 Dunwick Dr, Plano, TX 75023" onChange={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
<FormInput label="Type" value="Residential" onChange={() => { }} />
|
<FormInput id="prop-type" name="prop-type" label="Type" value="Residential" onChange={() => { }} />
|
||||||
<FormInput label="Year Built" value="1971" onChange={() => { }} />
|
<FormInput id="prop-year" name="prop-year" label="Year Built" value="1971" onChange={() => { }} />
|
||||||
<FormInput label="Built-Up Area (sqft)" value="2450" onChange={() => { }} />
|
<FormInput id="prop-area-sqft" name="prop-area-sqft" label="Built-Up Area (sqft)" value="2450" onChange={() => { }} />
|
||||||
<FormInput label="Lot Size (sqft)" value="8500" onChange={() => { }} />
|
<FormInput id="prop-lot-sqft" name="prop-lot-sqft" label="Lot Size (sqft)" value="8500" onChange={() => { }} />
|
||||||
<div className="grid grid-cols-3 gap-4 md:col-span-2">
|
<div className="grid grid-cols-3 gap-4 md:col-span-2">
|
||||||
<FormInput label="Beds" value="4" onChange={() => { }} />
|
<FormInput id="prop-beds" name="prop-beds" label="Beds" value="4" onChange={() => { }} />
|
||||||
<FormInput label="Baths" value="3" onChange={() => { }} />
|
<FormInput id="prop-baths" name="prop-baths" label="Baths" value="3" onChange={() => { }} />
|
||||||
<FormInput label="Parking" value="2" onChange={() => { }} />
|
<FormInput id="prop-parking" name="prop-parking" label="Parking" value="2" onChange={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,10 +190,10 @@ const CustomerProfile = () => {
|
|||||||
<ShieldCheck size={20} /> <h3 className="text-sm font-black uppercase tracking-widest">Insurance Information</h3>
|
<ShieldCheck size={20} /> <h3 className="text-sm font-black uppercase tracking-widest">Insurance Information</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<FormInput label="Insurance Company" value="Farmers" onChange={() => { }} />
|
<FormInput id="ins-company" name="ins-company" label="Insurance Company" value="Farmers" onChange={() => { }} />
|
||||||
<FormInput label="Policy Number" value="POL-987654321" onChange={() => { }} />
|
<FormInput id="ins-policy" name="ins-policy" label="Policy Number" value="POL-987654321" onChange={() => { }} />
|
||||||
<FormInput label="Claim Filed?" value="No" onChange={() => { }} />
|
<FormInput id="ins-claim-filed" name="ins-claim-filed" label="Claim Filed?" value="No" onChange={() => { }} />
|
||||||
<FormInput label="Adjuster Name" value="" placeholder="N/A" onChange={() => { }} />
|
<FormInput id="ins-adjuster" name="ins-adjuster" label="Adjuster Name" value="" placeholder="N/A" onChange={() => { }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -307,13 +309,15 @@ const ReadOnlyField = ({ label, value }) => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const FormInput = ({ label, icon: Icon, prefix, value, onChange, type = "text", required }) => (
|
const FormInput = ({ id, name, label, icon: Icon, prefix, value, onChange, type = "text", required }) => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold uppercase text-zinc-500">{label} {required && <span className="text-red-500">*</span>}</label>
|
<label htmlFor={id} className="text-xs font-bold uppercase text-zinc-500">{label} {required && <span className="text-red-500">*</span>}</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{Icon && <Icon className="absolute left-3 top-3 text-zinc-400" size={16} />}
|
{Icon && <Icon className="absolute left-3 top-3 text-zinc-400" size={16} />}
|
||||||
{prefix && <span className="absolute left-3 top-3 text-zinc-400 text-xs">{prefix}</span>}
|
{prefix && <span className="absolute left-3 top-3 text-zinc-400 text-xs">{prefix}</span>}
|
||||||
<input
|
<input
|
||||||
|
id={id}
|
||||||
|
name={name || id}
|
||||||
type={type}
|
type={type}
|
||||||
required={required}
|
required={required}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
+6
-2
@@ -204,7 +204,7 @@ const Login = () => {
|
|||||||
|
|
||||||
<form onSubmit={handleLogin} className="space-y-6 md:space-y-8">
|
<form onSubmit={handleLogin} className="space-y-6 md:space-y-8">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-bold text-zinc-500 ml-1 uppercase tracking-widest">
|
<label htmlFor="identifier" className="text-xs font-bold text-zinc-500 ml-1 uppercase tracking-widest">
|
||||||
{loginType === 'employee' ? 'Employee ID' : 'Username'}
|
{loginType === 'employee' ? 'Employee ID' : 'Username'}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
@@ -212,6 +212,8 @@ const Login = () => {
|
|||||||
{loginType === 'customer' ? <User size={20} /> : <Briefcase size={20} />}
|
{loginType === 'customer' ? <User size={20} /> : <Briefcase size={20} />}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
id="identifier"
|
||||||
|
name="identifier"
|
||||||
type="text"
|
type="text"
|
||||||
value={identifier}
|
value={identifier}
|
||||||
onChange={(e) => setIdentifier(e.target.value)}
|
onChange={(e) => setIdentifier(e.target.value)}
|
||||||
@@ -223,7 +225,7 @@ const Login = () => {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center ml-1">
|
<div className="flex justify-between items-center ml-1">
|
||||||
<label className="text-xs font-bold text-zinc-500 uppercase tracking-widest">Password</label>
|
<label htmlFor="password" className="text-xs font-bold text-zinc-500 uppercase tracking-widest">Password</label>
|
||||||
<button type="button" className="text-xs font-semibold text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 transition-colors">
|
<button type="button" className="text-xs font-semibold text-zinc-400 hover:text-zinc-600 dark:text-zinc-500 dark:hover:text-zinc-300 transition-colors">
|
||||||
Forgot Password?
|
Forgot Password?
|
||||||
</button>
|
</button>
|
||||||
@@ -233,6 +235,8 @@ const Login = () => {
|
|||||||
<Lock size={20} />
|
<Lock size={20} />
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
|||||||
@@ -576,7 +576,7 @@ const OwnerProjectDetail = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-white/5">
|
<tbody className="divide-y divide-white/5">
|
||||||
{project.changeOrders.map((co, i) => (
|
{project.changeOrders.map((co, i) => (
|
||||||
<tr key={i} className="hover:bg-white/5 transition-colors cursor-pointer" onClick={() => setSelectedCO(co)}>
|
<tr key={i} className="hover:bg-white/5 transition-colors cursor-pointer" tabIndex="0" onClick={() => setSelectedCO(co)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedCO(co); } }}>
|
||||||
<td className="px-5 py-4 font-mono text-xs text-zinc-500">{co.id}</td>
|
<td className="px-5 py-4 font-mono text-xs text-zinc-500">{co.id}</td>
|
||||||
<td className="px-5 py-4">
|
<td className="px-5 py-4">
|
||||||
<div className="font-bold text-sm text-white">{co.title}</div>
|
<div className="font-bold text-sm text-white">{co.title}</div>
|
||||||
@@ -741,7 +741,7 @@ const OwnerProjectDetail = () => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-white/5">
|
<tbody className="divide-y divide-white/5">
|
||||||
{project.invoices.map((inv, i) => (
|
{project.invoices.map((inv, i) => (
|
||||||
<tr key={i} className="hover:bg-white/5 transition-colors cursor-pointer" onClick={() => setSelectedInvoice(inv)}>
|
<tr key={i} className="hover:bg-white/5 transition-colors cursor-pointer" tabIndex="0" onClick={() => setSelectedInvoice(inv)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedInvoice(inv); } }}>
|
||||||
<td className="px-5 py-4 font-mono text-xs text-zinc-500">{inv.id}</td>
|
<td className="px-5 py-4 font-mono text-xs text-zinc-500">{inv.id}</td>
|
||||||
<td className="px-5 py-4 text-right font-mono text-sm font-bold text-[#00f0ff]">
|
<td className="px-5 py-4 text-right font-mono text-sm font-bold text-[#00f0ff]">
|
||||||
{formatCurrency(inv.amount)}
|
{formatCurrency(inv.amount)}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const OwnerProjectList = () => {
|
|||||||
// Filter projects for this owner
|
// Filter projects for this owner
|
||||||
const ownerProjects = useMemo(() =>
|
const ownerProjects = useMemo(() =>
|
||||||
projects.filter(p => p.ownerId === user?.id)
|
projects.filter(p => p.ownerId === user?.id)
|
||||||
, [projects, user]);
|
, [projects, user]);
|
||||||
|
|
||||||
// Apply filters
|
// Apply filters
|
||||||
const filteredProjects = useMemo(() => {
|
const filteredProjects = useMemo(() => {
|
||||||
@@ -125,8 +125,11 @@ const OwnerProjectList = () => {
|
|||||||
<div className="flex flex-col sm:flex-row gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
|
<label htmlFor="search-projects" className="sr-only">Search projects</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-projects"
|
||||||
|
name="search-projects"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by name, type, or ID..."
|
placeholder="Search by name, type, or ID..."
|
||||||
value={search}
|
value={search}
|
||||||
@@ -139,11 +142,10 @@ const OwnerProjectList = () => {
|
|||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setStatusFilter('all')}
|
onClick={() => setStatusFilter('all')}
|
||||||
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider transition-colors ${
|
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider transition-colors ${statusFilter === 'all'
|
||||||
statusFilter === 'all'
|
|
||||||
? 'bg-zinc-900 text-white dark:bg-white dark:text-black'
|
? 'bg-zinc-900 text-white dark:bg-white dark:text-black'
|
||||||
: 'bg-zinc-100 text-zinc-500 hover:bg-zinc-200 dark:bg-white/5 dark:text-zinc-400 dark:hover:bg-white/10'
|
: 'bg-zinc-100 text-zinc-500 hover:bg-zinc-200 dark:bg-white/5 dark:text-zinc-400 dark:hover:bg-white/10'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
All
|
All
|
||||||
</button>
|
</button>
|
||||||
@@ -151,11 +153,10 @@ const OwnerProjectList = () => {
|
|||||||
<button
|
<button
|
||||||
key={s}
|
key={s}
|
||||||
onClick={() => setStatusFilter(s)}
|
onClick={() => setStatusFilter(s)}
|
||||||
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider transition-colors ${
|
className={`px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-wider transition-colors ${statusFilter === s
|
||||||
statusFilter === s
|
|
||||||
? 'bg-zinc-900 text-white dark:bg-white dark:text-black'
|
? 'bg-zinc-900 text-white dark:bg-white dark:text-black'
|
||||||
: 'bg-zinc-100 text-zinc-500 hover:bg-zinc-200 dark:bg-white/5 dark:text-zinc-400 dark:hover:bg-white/10'
|
: 'bg-zinc-100 text-zinc-500 hover:bg-zinc-200 dark:bg-white/5 dark:text-zinc-400 dark:hover:bg-white/10'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{s.replace('_', ' ')}
|
{s.replace('_', ' ')}
|
||||||
</button>
|
</button>
|
||||||
@@ -163,16 +164,21 @@ const OwnerProjectList = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phase Filter */}
|
{/* Phase Filter */}
|
||||||
<select
|
<div>
|
||||||
value={phaseFilter}
|
<label htmlFor="filter-phase" className="sr-only">Filter by phase</label>
|
||||||
onChange={(e) => setPhaseFilter(e.target.value)}
|
<select
|
||||||
className="px-3 py-2 rounded-xl text-sm bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
id="filter-phase"
|
||||||
>
|
name="filter-phase"
|
||||||
<option value="all">All Phases</option>
|
value={phaseFilter}
|
||||||
{phaseOrder.filter(p => availablePhases.includes(p)).map(p => (
|
onChange={(e) => setPhaseFilter(e.target.value)}
|
||||||
<option key={p} value={p}>{p}</option>
|
className="px-3 py-2 rounded-xl text-sm bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
|
||||||
))}
|
>
|
||||||
</select>
|
<option value="all">All Phases</option>
|
||||||
|
{phaseOrder.filter(p => availablePhases.includes(p)).map(p => (
|
||||||
|
<option key={p} value={p}>{p}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SpotlightCard>
|
</SpotlightCard>
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,11 @@ const PeopleDirectory = () => {
|
|||||||
<SpotlightCard className={`w-full lg:w-1/3 flex flex-col overflow-hidden ${selectedPerson ? 'hidden lg:flex' : 'flex'}`}>
|
<SpotlightCard className={`w-full lg:w-1/3 flex flex-col overflow-hidden ${selectedPerson ? 'hidden lg:flex' : 'flex'}`}>
|
||||||
<div className="p-4 border-b border-zinc-200 dark:border-white/5 bg-zinc-50/50 dark:bg-white/5 space-y-4">
|
<div className="p-4 border-b border-zinc-200 dark:border-white/5 bg-zinc-50/50 dark:bg-white/5 space-y-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
<label htmlFor="search-people" className="sr-only">Search by name or email</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-people"
|
||||||
|
name="search-people"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by name or email..."
|
placeholder="Search by name or email..."
|
||||||
value={search}
|
value={search}
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ const VendorDirectory = () => {
|
|||||||
<SpotlightCard className={`w-full lg:w-1/3 flex flex-col overflow-hidden ${selectedVendor ? 'hidden lg:flex' : 'flex'}`}>
|
<SpotlightCard className={`w-full lg:w-1/3 flex flex-col overflow-hidden ${selectedVendor ? 'hidden lg:flex' : 'flex'}`}>
|
||||||
<div className="p-4 border-b border-zinc-200 dark:border-white/5 space-y-4 bg-zinc-50/50 dark:bg-white/5">
|
<div className="p-4 border-b border-zinc-200 dark:border-white/5 space-y-4 bg-zinc-50/50 dark:bg-white/5">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
<label htmlFor="search-vendors" className="sr-only">Search vendors</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-vendors"
|
||||||
|
name="search-vendors"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search vendors..."
|
placeholder="Search vendors..."
|
||||||
value={search}
|
value={search}
|
||||||
|
|||||||
Vendored
+3
@@ -41,8 +41,11 @@ const VendorOrders = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
<label htmlFor="search-vendor-orders" className="sr-only">Search orders</label>
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={16} />
|
||||||
<input
|
<input
|
||||||
|
id="search-vendor-orders"
|
||||||
|
name="search-vendor-orders"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search orders..."
|
placeholder="Search orders..."
|
||||||
className="pl-9 pr-4 py-2 rounded-lg bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50"
|
className="pl-9 pr-4 py-2 rounded-lg bg-zinc-100 dark:bg-white/5 border border-zinc-200 dark:border-white/10 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50"
|
||||||
|
|||||||
Reference in New Issue
Block a user