import React, { useMemo, useState } from 'react';
import {
Tabs,
TextInput,
Select,
MultiSelect,
Group,
Badge,
Card,
Text,
ScrollArea,
Stack,
} from '@mantine/core';
/**
* Experimental component: MemoExplorer
* -----------------------------------
* Stand-alone UI that renders creator/approver/responder memo lists with
* client-side search & filtering based on the *info data* shape supplied by backend.
*
* Expected data prop shape (example):
* {
* creator: {
* history: [ { id, complaint, department, ... }, ... ],
* ongoing: [...],
* sameWard: [...]
* },
* approver: { waitingApproval: [...], previousBlocks: [...], completed: [...] },
* responder: { attending: [...], tagged: [...], completed: [...] }
* }
*/
const MemoExplorer = ({ data }) => {
// Accept either the expected role-based object OR a flat array (backward compatibility)
const normalizedData = useMemo(() => {
if (Array.isArray(data)) {
return { default: { list: data } }; // one role("default") with one tab("list")
}
return data || {};
}, [data]);
/* --------- Roles & Tabs --------- */
const roles = useMemo(() => Object.keys(normalizedData), [normalizedData]);
const [activeRole, setActiveRole] = useState(roles[0] || '');
const tabsForRole = useMemo(() => {
return activeRole && normalizedData[activeRole] ? Object.keys(normalizedData[activeRole]) : [];
}, [activeRole, normalizedData]);
const [activeTab, setActiveTab] = useState(tabsForRole[0] || '');
/* -------------------------------------------------- */
/* Filters */
/* -------------------------------------------------- */
const currentMemos = useMemo(() => {
if (!activeRole || !activeTab) return [];
return normalizedData?.[activeRole]?.[activeTab] || [];
}, [normalizedData, activeRole, activeTab]);
/* Build unique filter options dynamically */
const wardOptions = useMemo(
() => [...new Set(currentMemos.map((m) => m.current_ward_name).filter(Boolean))]
.map((w) => ({ value: String(w), label: String(w) })),
[currentMemos]
);
const departmentOptions = useMemo(
() => [...new Set(currentMemos.map((m) => m.department).filter(Boolean))]
.map((d) => ({ value: String(d), label: String(d) })),
[currentMemos]
);
const [searchTerm, setSearchTerm] = useState('');
const [selectedWards, setSelectedWards] = useState([]);
const [selectedDepartments, setSelectedDepartments] = useState([]);
/* ---------------- Filter logic -------------------- */
const filteredMemos = useMemo(() => {
return currentMemos.filter((memo) => {
// Search by complaint text (case-insensitive)
const matchesSearch = searchTerm
? memo.complaint.toLowerCase().includes(searchTerm.toLowerCase())
: true;
const matchesWard = selectedWards.length > 0 ? selectedWards.includes(memo.current_ward_name) : true;
const matchesDept =
selectedDepartments.length > 0 ? selectedDepartments.includes(memo.department) : true;
return matchesSearch && matchesWard && matchesDept;
});
}, [currentMemos, searchTerm, selectedWards, selectedDepartments]);
/* ---------------- Sync role/tab on data change ------------------ */
React.useEffect(() => {
if (!roles.includes(activeRole)) {
setActiveRole(roles[0] || '');
}
}, [roles]);
React.useEffect(() => {
if (!tabsForRole.includes(activeTab)) {
setActiveTab(tabsForRole[0] || '');
}
}, [tabsForRole]);
/* ---------------- Render helpers ------------------ */
const renderMemoCard = (memo) => (
<Card key={memo.id} shadow="sm" p="md" radius="md" withBorder>
<Group position="apart" mb="xs">
<Text fw={700}>{memo.complaint}</Text>
<Badge color="blue" variant="light">
{memo.department}
</Badge>
</Group>
<Text size="sm" c="dimmed">
Ward: {memo.current_ward_name} | Block: {memo.current_block_name}
</Text>
<Text size="xs" c="dimmed">
Raised by user #{memo.user_id} ({memo.user_role_name})
</Text>
</Card>
);
return (
<Stack>
{/* Role Tabs */}
<Tabs value={activeRole} onTabChange={setActiveRole} variant="outline">
<Tabs.List>
{roles.map((role) => (
<Tabs.Tab key={role} value={role}>
{role}
</Tabs.Tab>
))}
</Tabs.List>
</Tabs>
{/* Tab group for the selected role */}
{activeRole && (
<Tabs value={activeTab} onTabChange={setActiveTab} variant="pills">
<Tabs.List mb="sm">
{tabsForRole.map((tab) => (
<Tabs.Tab key={tab} value={tab}>
{tab}
</Tabs.Tab>
))}
</Tabs.List>
</Tabs>
)}
{/* Filters */}
<Group align="flex-end" grow>
<TextInput
label="Search"
placeholder="Search complaints..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.currentTarget.value)}
/>
<MultiSelect
label="Ward"
data={wardOptions}
placeholder="Filter by ward"
value={selectedWards}
onChange={setSelectedWards}
clearable
/>
<MultiSelect
label="Department"
data={departmentOptions}
placeholder="Filter by department"
value={selectedDepartments}
onChange={setSelectedDepartments}
clearable
/>
</Group>
{/* Memos list */}
<ScrollArea offsetScrollbars style={{ height: 500 }}>
<Stack>
{filteredMemos.length === 0 && <Text size="sm">No memos match your criteria.</Text>}
{filteredMemos.map(renderMemoCard)}
</Stack>
</ScrollArea>
</Stack>
);
};
export default MemoExplorer;
Source