Source

components/dashboard/DashboardContent.jsx

import React, { useState, useEffect } from 'react';
import { useAuth } from '../../hooks/useAuth';
import CrudTable from '../common/CrudTable';
import Modal from '../ui/Modal';
import UserForm from '../forms/UserForm';
import RoleForm from '../forms/RoleForm';
import BlockForm from '../forms/BlockForm';
import ShiftForm from '../forms/ShiftForm';
import ApproverHierarchyForm from '../forms/ApproverHierarchyForm';
import ConfirmDialog from '../modals/ConfirmDialog';
import { useCrud } from '../../hooks/useCrud';
import { useOutletContext } from 'react-router-dom';
import DashboardIframe from './DashboardIframe';

/**
 * DashboardContent component displays different content based on the active tab.
 * It includes CRUD operations for various entities (users, roles, blocks, etc.)
 * and a dashboard view for superusers.
 *
 * It leverages `useCrud` hook for data fetching and manipulation, and
 * displays data using `CrudTable`. Modals are used for add/edit forms and
 * a confirmation dialog for deletions.
 *
 * @param {object} props - The component props.
 * @param {string} [props.activeTab='dashboard'] - The currently active tab in the dashboard.
 */
const DashboardContent = ({ activeTab: _propActiveTab = 'dashboard' }) => {
  const outletCtx = useOutletContext();
  const activeTab = outletCtx?.activeTab ?? _propActiveTab;
  const { isSuperuser, user } = useAuth();
  const isStaff = user?.is_staff;
  const crudHook = useCrud(activeTab);
  const { data, loading, fetchData, createItem, updateItem, deleteItem } = crudHook;

  const [modalState, setModalState] = useState({ isOpen: false, mode: 'create', data: null });
  const [confirmDialog, setConfirmDialog] = useState({ isOpen: false, item: null, loading: false });
  const [searchTerm, setSearchTerm] = useState('');
  const [sortOrder, setSortOrder] = useState('asc');
  const [selectedResponder, setSelectedResponder] = useState('');

  useEffect(() => {
    setSearchTerm("");
    setSelectedResponder("");
    if ((isSuperuser || isStaff) && activeTab !== 'dashboard' && activeTab !== 'settings') {
      fetchData();
    }
  }, [activeTab, isSuperuser, isStaff, fetchData]);

  /**
   * Handles the click event for adding a new item.
   * Opens the modal in 'create' mode.
   */
  const handleAdd = () => setModalState({ isOpen: true, mode: 'create', data: null });

  /**
   * Handles the click event for editing an existing item.
   * Opens the modal in 'edit' mode with the item data.
   * @param {object} item - The item to be edited.
   */
  const handleEdit = (item) => setModalState({ isOpen: true, mode: 'edit', data: item });

  const handleDelete = (item) => setConfirmDialog({ isOpen: true, item, loading: false });


  const confirmDelete = async () => {
    setConfirmDialog(prev => ({ ...prev, loading: true }));
    const result = await deleteItem(confirmDialog.item.id);
    result.success
      ? setConfirmDialog({ isOpen: false, item: null, loading: false })
      : setConfirmDialog(prev => ({ ...prev, loading: false })) || alert('Failed to delete item: ' + result.error);
  };

  /**
   * Handles the submission of the form within the modal.
   * Calls createItem or updateItem based on the modal mode.
   */

  const handleFormSubmit = async (formData) => {
    const result = modalState.mode === 'create'
      ? await createItem(formData)
      : await updateItem(modalState.data.id, formData);

    result.success
      ? setModalState({ isOpen: false, mode: 'create', data: null })
      : alert(`Failed to ${modalState.mode} item: ` + result.error);
  };

  /**
   * Closes the modal and resets its state.
   */
  const closeModal = () => setModalState({ isOpen: false, mode: 'create', data: null });

  /**
   * Returns the appropriate form component based on the active tab.
   * @returns {React.Component | null} The form component or null if no form is defined for the tab.
   */
  const getFormComponent = () => {
    const commonProps = { initialData: modalState.data, onSubmit: handleFormSubmit, onCancel: closeModal, loading: false };
    
    switch (activeTab) {
      case 'users': return <UserForm {...commonProps} />;
      case 'roles': return <RoleForm {...commonProps} />;
      case 'blocks': return <BlockForm {...commonProps} />;
      case 'hierarchies': return <ApproverHierarchyForm {...commonProps} />;
      case 'shifts': return <ShiftForm {...commonProps} />;
      default: return null;
    }
  };

  /**
   * Defines the columns configuration for the CrudTable based on the active tab.
   * Includes sort buttons for some columns.
   */
  const getColumns = () => {
    const addSortButton = (key) => (
      <button onClick={() => setSortOrder(prev => prev === 'asc' ? 'desc' : 'asc')} className="ml-2 text-blue-500">⇅</button>
    );

    switch (activeTab) {
      case 'users':
        return [
          { key: 'institution_id', label: <>Name {addSortButton('institution_id')}</> },
          { key: 'role_name', label: <>Role {addSortButton('role_name')}</>, render: (value) => value || 'N/A' },
          { key: 'phone_number', label: <>Phone {addSortButton('phone_number')}</>, render: (value) => value || 'N/A' },
          { key: 'current_block_name', label: <>Current Block {addSortButton('current_block_name')}</> },
           { key: 'date_joined', label: 'Joined Date', render: (value) => value ? new Date(value).toLocaleDateString() : 'N/A' }

        ];
      case 'roles':
        return [
          { key: 'name', label: <>Name {addSortButton('name')}</> },
          { key: 'description', label: <>Description {addSortButton('description')}</>, render: (value) => value || 'N/A' },
          { key: 'is_approver', label: 'Approver', render: (value) => value ? 'Yes' : 'No' },
          { key: 'is_responder', label: 'Responder', render: (value) => value ? 'Yes' : 'No' },
          { key: 'is_creator', label: 'Creator', render: (value) => value ? 'Yes' : 'No' },
        ];
      case 'hierarchies':
        return [
          { key: 'id', label: 'ID' },
          { key: 'is_active', label: 'Active', render: (val) => (val ? 'Yes' : 'No') },
          { key: 'levels', label: 'Levels', render: (levels) => levels?.length || 0 },
        ];
      case 'blocks':
        return [
          { key: 'name', label: <>Name {addSortButton('name')}</> },
          { key: 'no_of_floors', label: 'Floors' },
          { key: 'created_by', label: 'Created By' },
          { key: 'created_at', label: 'Created', render: (value) => new Date(value).toLocaleDateString() },
        ];
      case 'shifts':
        return [
          { key: 'name', label: 'Name' },
          { key: 'start_time', label: 'Start Time' },
          { key: 'end_time', label: 'End Time' },
        ];
      default:
        return [];
    }
  };

  /**
   * Filters and sorts the data based on the current search term, selected responder, and sort order.
   * Handles sorting for date fields specifically.
   * @returns {Array<object>} The filtered and sorted data.
   */
  const filteredData = (data ?? []) 
    ?.filter(item => {
      const matchSearch = Object.values(item).some(value =>
        value?.toString().toLowerCase().includes(searchTerm.toLowerCase())
      );
      const matchResponder = selectedResponder ? item?.role_name === selectedResponder : true;
      return matchSearch && matchResponder;
    })
    .sort((a, b) => {
  const columns = getColumns();
  const firstKey = columns[0]?.key ?? "";
  if (!firstKey) return 0;

  const valA = a[firstKey];
  const valB = b[firstKey];

  if (valA == null || valB == null) return 0;

  // Check if the field is a date field
  const isDateField = ['date_joined', 'created_at'].includes(firstKey);

  if (isDateField) {
    const dateA = new Date(valA);
    const dateB = new Date(valB);
    return sortOrder === 'asc' ? dateA - dateB : dateB - dateA;
  }

  const strA = valA.toString().toLowerCase?.() ?? '';
  const strB = valB.toString().toLowerCase?.() ?? '';
  return sortOrder === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
});

  const responderOptions = [...new Set((data ?? []).map(item => item.role_name).filter(Boolean))];

  // Handle dashboard view separately
  if (activeTab === 'dashboard') {
    return (
      <div className="p-4">
        {isSuperuser ? (
          <DashboardIframe />
        ) : (
          <div className="text-center py-8">
            <h2 className="text-2xl font-bold text-blue-800 mb-4">Welcome to MemoTrack</h2>
            <p className="text-gray-600">Your workspace for managing memos and communications.</p>
          </div>
        )}
      </div>
    );
  }

  return (
    <>
      <div className="p-4">
        <div className="flex flex-wrap justify-between items-center mb-4 gap-4">
          <h2 className="text-xl font-semibold">{activeTab.charAt(0).toUpperCase() + activeTab.slice(1)}</h2>
          <div className="flex gap-3 items-center">
            {activeTab === 'users' && (
              <select
                className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5"
                value={selectedResponder}
                onChange={(e) => setSelectedResponder(e.target.value)}
              >
                <option value="">All Responders</option>
                {responderOptions.map(role => (
                  <option key={role} value={role}>{role}</option>
                ))}
              </select>
            )}
            <input
              type="text"
              placeholder="Search..."
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              className="w-50 px-4 py-1 text-lg text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500"
            />
          </div>
        </div>

        <CrudTable
          title={`${activeTab.charAt(0).toUpperCase() + activeTab.slice(1)}`}
          data={filteredData}
          columns={getColumns()}
          onAdd={handleAdd}
          onEdit={handleEdit}
          onDelete={handleDelete}
          loading={loading}
        />
      </div>

      <Modal
        isOpen={modalState.isOpen}
        onClose={closeModal}
        title={`${modalState.mode === 'create' ? 'Add' : 'Edit'} ${activeTab.slice(0, -1)}`}
        size="lg"
      >
        {getFormComponent()}
      </Modal>

      <ConfirmDialog
        isOpen={confirmDialog.isOpen}
        onClose={() => setConfirmDialog({ isOpen: false, item: null, loading: false })}
        onConfirm={confirmDelete}
        title="Delete Item"
        message={`Are you sure you want to delete this ${activeTab.slice(0, -1)}? This action cannot be undone.`}
        confirmText="Delete"
        loading={confirmDialog.loading}
      />
    </>
  );
};

export default DashboardContent;