import { Building, ChevronRight, MapPin } from 'lucide-react';
import { useMemo, useState } from 'react';
/**
* A tabbed list component for displaying and filtering memos.
*
* @param {object} props - The component props.
* @param {object|Array} props.data - The memo data, either an array or an object with tab keys.
* @param {function} [props.onMemoClick] - Callback function when a memo card is clicked.
**/
const MemoTabbedList = ({ data, onMemoClick }) => {
const tabMap = useMemo(() => {
if (!data) return {};
if (Array.isArray(data)) {
const flat = data.map((m) => ({ ...m, ...m.latest_snapshot }));
return { All: flat };
}
const firstKey = Object.keys(data)[0] || 'default';
return data[firstKey] || {};
}, [data]);
const tabNames = Object.keys(tabMap);
const [activeTab, setActiveTab] = useState(tabNames[0] || '');
const allMemos = tabMap[activeTab] || [];
const unique = (arr) => [...new Set(arr.filter(Boolean))];
const wardOptions = unique(allMemos.map((m) => m.latest_snapshot.current_ward_name));
const blockOptions = unique(allMemos.map((m) => m.latest_snapshot.current_block_name));
const floorOptions = unique(allMemos.map((m) => m.latest_snapshot.current_floor));
const deptOptions = unique(allMemos.map((m) => m.latest_snapshot.department));
const [searchInput, setSearchInput] = useState('');
const [wardInput, setWardInput] = useState('');
const [blockInput, setBlockInput] = useState('');
const [floorInput, setFloorInput] = useState('');
const [deptInput, setDeptInput] = useState('');
const [dateInput, setDateInput] = useState('');
const [search, setSearch] = useState('');
const [wardFilter, setWardFilter] = useState('');
const [blockFilter, setBlockFilter] = useState('');
const [floorFilter, setFloorFilter] = useState('');
const [deptFilter, setDeptFilter] = useState('');
const [dateFilter, setDateFilter] = useState('');
const [filtersVisible, setFiltersVisible] = useState(false);
const applyFilters = () => {
setSearch(searchInput);
setWardFilter(wardInput);
setBlockFilter(blockInput);
setFloorFilter(floorInput);
setDeptFilter(deptInput);
setDateFilter(dateInput);
};
const resetFilters = () => {
setSearchInput('');
setWardInput('');
setBlockInput('');
setFloorInput('');
setDeptInput('');
setDateInput('');
setSearch('');
setWardFilter('');
setBlockFilter('');
setFloorFilter('');
setDeptFilter('');
setDateFilter('');
};
const filtered = allMemos.filter((m) => {
const dateOnly = (d) => new Date(d).toISOString().split('T')[0];
const textOk = search ? (m.latest_snapshot.complaint || '').toLowerCase().includes(search.toLowerCase()) : true;
const wardOk = wardFilter ? m.latest_snapshot.current_ward_name === wardFilter : true;
const deptOk = deptFilter ? m.latest_snapshot.department === deptFilter : true;
const blockOk = blockFilter ? m.latest_snapshot.current_block_name === blockFilter : true;
const floorOk = floorFilter ? String(m.latest_snapshot.current_floor) === String(floorFilter) : true;
const dateOk = dateFilter ? dateOnly(m.created_at) === dateFilter : true;
return textOk && wardOk && deptOk && blockOk && floorOk && dateOk;
});
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const getStatusColor = (taggedRoleName) => {
const colors = {
'nursing superintendent': 'bg-blue-100 text-blue-800',
doctor: 'bg-green-100 text-green-800',
admin: 'bg-purple-100 text-purple-800',
default: 'bg-gray-100 text-gray-800',
};
return colors[taggedRoleName?.toLowerCase()] || colors.default;
};
function getDisplayStatusColor(status) {
switch (status.toLowerCase()) {
case 'completed':
return 'bg-green-100 text-green-800';
case 'attended':
return 'bg-blue-100 text-blue-800';
case 'incomplete':
return 'bg-red-100 text-red-800';
case 'tagged':
return 'bg-yellow-100 text-yellow-800';
default:
return 'bg-gray-100 text-gray-700'; // for 'pending' or unknown
}
}
return (
<div className="space-y-4">
{/* Tabs */}
<div className="flex gap-2 flex-wrap">
{tabNames.map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-3 py-1 rounded-md text-sm font-medium transition-colors ${tab === activeTab ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{tab}
</button>
))}
</div>
{/* Filter Toggle Button */}
<div>
<button
className="bg-blue-100 text-blue-700 px-4 py-2 rounded-md text-sm mb-2"
onClick={() => setFiltersVisible((prev) => !prev)}
>
{filtersVisible ? 'Hide Filters' : 'Memo Filter'}
</button>
</div>
{/* Filters */}
{filtersVisible && (
<div className="bg-gray-50 border rounded-md p-4 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-6 gap-3">
<input
type="text"
placeholder="Search complaint"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
className="border rounded-md px-3 py-2 text-sm w-full"
/>
<select value={wardInput} onChange={(e) => setWardInput(e.target.value)} className="border rounded-md px-3 py-2 text-sm w-full">
<option value="">All Wards</option>
{wardOptions.map((w) => <option key={w} value={w}>{w}</option>)}
</select>
<select value={deptInput} onChange={(e) => setDeptInput(e.target.value)} className="border rounded-md px-3 py-2 text-sm w-full">
<option value="">All Departments</option>
{deptOptions.map((d) => <option key={d} value={d}>{d}</option>)}
</select>
<select value={blockInput} onChange={(e) => setBlockInput(e.target.value)} className="border rounded-md px-3 py-2 text-sm w-full">
<option value="">All Blocks</option>
{blockOptions.map((b) => <option key={b} value={b}>{b}</option>)}
</select>
<select value={floorInput} onChange={(e) => setFloorInput(e.target.value)} className="border rounded-md px-3 py-2 text-sm w-full">
<option value="">All Floors</option>
{floorOptions.map((f) => <option key={f} value={f}>{f}</option>)}
</select>
<input
type="date"
value={dateInput}
onChange={(e) => setDateInput(e.target.value)}
className="border rounded-md px-3 py-2 text-sm w-full"
/>
</div>
<div className="flex gap-2 justify-end">
<button
onClick={resetFilters}
className="text-gray-600 border border-gray-300 px-4 py-2 rounded-md text-sm hover:bg-gray-100"
>
Reset Filters
</button>
<button
onClick={applyFilters}
className="bg-blue-600 text-white px-4 py-2 rounded-md text-sm"
>
Apply Filters
</button>
</div>
</div>
)}
{/* Memo Cards */}
<div className="space-y-3 max-h-[500px] overflow-y-auto pr-1">
{filtered.length === 0 ? (
<p className="text-sm text-gray-500">No memos match your criteria.</p>
) : (
filtered.map((memo) => {
const snapshot = memo.latest_snapshot;
const status = snapshot.display_status ||snapshot.status || 'pending';
return (
<div
key={memo.memoId}
className="bg-white rounded-lg shadow-sm border border-gray-200 p-3 active:bg-gray-50 transition-all cursor-pointer hover:shadow-md"
onClick={() => onMemoClick?.(memo)}
>
<div className="flex justify-between items-center mb-2">
<div className="flex items-center gap-2">
<span className={`text-xs px-2 py-1 rounded-full font-medium ${getStatusColor(snapshot.tagged_role_name)}`}>
{snapshot.tagged_role_name || 'Unassigned'}
</span>
<span className="text-xs text-gray-500">{formatDate(memo.created_at)}</span>
</div>
<ChevronRight className="w-4 h-4 text-gray-400" />
</div>
<h3 className="font-medium text-gray-900 mb-2 line-clamp-2">
{snapshot.complaint || 'No complaint description available'}
</h3>
{/* Status badge */}
<div className="mb-2">
<span className={`inline-block text-xs px-2 py-1 rounded-full font-semibold capitalize ${getDisplayStatusColor(status)}`}>
{status}
</span>
</div>
<div className="flex items-center gap-4 text-sm text-gray-600">
<span className="flex items-center gap-1">
<Building className="w-3 h-3" />
{snapshot.department}
</span>
<span className="flex items-center gap-1">
<MapPin className="w-3 h-3" />
{snapshot.current_ward_name}
</span>
</div>
</div>
);
})
)}
</div>
</div>
);
};
export default MemoTabbedList;
Source