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;
Source