Source

components/prototype/MemoExplorer.jsx

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;