Skip to content

Commit

Permalink
feat(4853): enhance Tasks admin page
Browse files Browse the repository at this point in the history
  • Loading branch information
junminahn committed Feb 5, 2025
1 parent 5327534 commit 411aa58
Show file tree
Hide file tree
Showing 16 changed files with 268 additions and 254 deletions.
2 changes: 1 addition & 1 deletion app/app/events/all/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default eventsPage(() => {
return (
<>
<Table
title="Events in Registry"
title="Events"
totalCount={totalCount}
page={snap.page ?? 1}
pageSize={snap.pageSize ?? 10}
Expand Down
39 changes: 19 additions & 20 deletions app/app/tasks/all/FilterPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Button, LoadingOverlay, Box } from '@mantine/core';
import { TaskType } from '@prisma/client';
import { startCase } from 'lodash-es';
import { TaskStatus, TaskType } from '@prisma/client';
import { useSnapshot } from 'valtio';
import FormMultiSelect from '@/components/generic/select/FormMultiSelect';
import { taskTypeNames } from '@/constants/task';
import { taskTypeMap } from '@/constants';
import { pageState } from './state';

const taskTypeOptions = Object.entries(taskTypeNames).map(([key, value]) => ({
const taskTypeOptions = Object.entries(taskTypeMap).map(([key, value]) => ({
value: key,
label: value,
}));
Expand All @@ -23,10 +22,10 @@ export default function FilterPanel({ isLoading = false }: { isLoading?: boolean
loaderProps={{ color: 'pink', type: 'bars' }}
/>
<div className="grid grid-cols-1 gap-y-2 md:grid-cols-12 md:gap-x-3">
<div className="col-span-12">
<div className="col-span-6">
<FormMultiSelect
name="tasks"
label="Task Types"
name="types"
label="Types"
value={pageSnapshot.types ?? []}
data={taskTypeOptions}
onChange={(value) => {
Expand All @@ -35,19 +34,19 @@ export default function FilterPanel({ isLoading = false }: { isLoading?: boolean
}}
classNames={{ wrapper: '' }}
/>
<div className="text-right">
<Button
color="primary"
size="compact-md"
className="mt-1"
onClick={() => {
pageState.types = taskTypeOptions.map((option) => option.value as TaskType);
pageState.page = 1;
}}
>
Select All
</Button>
</div>
</div>
<div className="col-span-6">
<FormMultiSelect
name="statuses"
label="Statuses"
value={pageSnapshot.statuses ?? []}
data={Object.values(TaskStatus)}
onChange={(value) => {
pageState.statuses = value as TaskStatus[];
pageState.page = 1;
}}
classNames={{ wrapper: '' }}
/>
</div>
</div>
</Box>
Expand Down
199 changes: 82 additions & 117 deletions app/app/tasks/all/TableBody.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,87 @@
'use client';

import { Avatar, Badge, Code, Group, Table, Text } from '@mantine/core';
import { Badge, Table, Text } from '@mantine/core';
import { TaskStatus } from '@prisma/client';
import { startCase } from 'lodash-es';
import { useForm } from 'react-hook-form';
import MinistryBadge from '@/components/badges/MinistryBadge';
import { ExtendedTask, statusColorMap, taskTypeNames, UserInfo } from '@/constants/task';
import { formatFullName } from '@/helpers/user';
import { getUserImageData } from '@/helpers/user-image';
import { ReactNode } from 'react';
import KeyValueTable from '@/components/generic/KeyValueTable';
import UserProfile from '@/components/users/UserProfile';
import { taskTypeMap } from '@/constants/task';
import { SearchTask } from '@/types/task';
import { formatDate } from '@/utils/js';

interface TableProps {
data: ExtendedTask[];
assignees: UserInfo[];
data: SearchTask[];
}

export default function TableBody({ data, assignees }: TableProps) {
const methods = useForm({
values: {
tasks: data,
taskAssignees: assignees,
},
});
function Assignees({ task }: { task: SearchTask }) {
const result: ReactNode[] = [];
if (task.roles?.length > 0) {
result.push(
<>
<Text c="dimmed" size="sm" className="font-semibold">
Roles
</Text>
<ul className="mb-3">
{task.roles.map((role, index) => (
<li key={index}>
<Badge autoContrast={true} size="sm" color="gray">
{startCase(role)}
</Badge>
</li>
))}
</ul>
</>,
);
}

if (task.permissions?.length > 0) {
result.push(
<>
<Text c="dimmed" size="sm" className="font-semibold">
Permissions
</Text>
<ul className="mb-3">
{task.permissions.map((permission, index) => (
<li key={index}>
<Badge autoContrast={true} size="sm" color="gray">
{startCase(permission)}
</Badge>
</li>
))}
</ul>
</>,
);
}

if (task.users?.length > 0) {
result.push(
<>
<Text c="dimmed" size="sm" className="font-semibold">
Users
</Text>
{task.users.map((user) => (
<UserProfile key={user.id} data={user} />
))}
</>,
);
}

result.push(
<Text className="mt-2 italic" size="sm">
At: {formatDate(task.createdAt)}
</Text>,
);

const [tasks, taskAssignees] = methods.watch(['tasks', 'taskAssignees']);
return result;
}

export default function TableBody({ data }: TableProps) {
const rows =
tasks.length && taskAssignees ? (
tasks.map((task) => (
data.length > 0 ? (
data.map((task) => (
<Table.Tr key={task.id}>
<Table.Td>{taskTypeNames[task.type]}</Table.Td>
<Table.Td>{taskTypeMap[task.type]}</Table.Td>
<Table.Td>
{task.status === TaskStatus.COMPLETED ? (
<Badge color="green">{task.status}</Badge>
Expand All @@ -38,107 +90,22 @@ export default function TableBody({ data, assignees }: TableProps) {
)}
</Table.Td>
<Table.Td>
{task.closedMetadata && Object.keys(task.closedMetadata).length !== 0 && (
<ul>
{Object.entries(task.closedMetadata).map(([key, value]) => (
<li key={key}>
<Badge color={statusColorMap[value] || 'teal'}>
{typeof value === 'object' ? JSON.stringify(value) : value.toString()}
</Badge>
</li>
))}
</ul>
)}
<Assignees task={task} />
</Table.Td>
<Table.Td>
{task.roles && task.roles?.length !== 0 && (
<>
<Text c="dimmed" size="sm" className="font-semibold">
Roles:
</Text>
<ul className="mb-3">
{task.roles.map((role, index) => (
<li key={index}>
<Badge autoContrast={true} size="sm" color="gray">
{startCase(role)}
</Badge>
</li>
))}
</ul>
</>
)}

{task.permissions?.length > 0 && (
<>
<Text c="dimmed" size="sm" className="font-semibold">
Permissions:
{task.completedByUser && (
<UserProfile data={task.completedByUser}>
<Text className="mt-2 italic" size="sm">
At: {formatDate(task.completedAt)}
</Text>
<ul>
{task.permissions.map((permission, index) => (
<li key={index}>
<Text size="sm">
<Badge autoContrast={true} size="sm" color="gray">
{startCase(permission)}
</Badge>
</Text>
</li>
))}
</ul>
</>
)}
</Table.Td>
<Table.Td>
{task.userIds.length > 0 && (
<ul>
{task.userIds.map((id, index) => {
const userInfo = taskAssignees[index];
if (!userInfo) return null;
return (
<li className="my-5" key={index}>
<Avatar src={getUserImageData(userInfo.image)} size={36} radius="xl" />
<div>
<Text size="sm" className="font-semibold">
<div>
{formatFullName(userInfo)}
<MinistryBadge className="ml-1" ministry={userInfo.ministry} />
</div>
</Text>
<Text size="xs" opacity={0.5}>
{userInfo.email}
</Text>
</div>
</li>
);
})}
</ul>
</UserProfile>
)}
</Table.Td>
<Table.Td>
<Code block>{JSON.stringify(task.data, null, 2)}</Code>
<KeyValueTable data={task.data || {}} />
</Table.Td>
<Table.Td className="italic">{formatDate(task.createdAt)}</Table.Td>
<Table.Td>
<Group gap="sm" className="cursor-pointer" onClick={async () => {}}>
{task.completedByUser && (
<>
<Avatar src={getUserImageData(task.completedByUser.image)} size={36} radius="xl" />
<div>
<Text size="sm" className="font-semibold">
<div>
{formatFullName(task.completedByUser)}
<MinistryBadge className="ml-1" ministry={task.completedByUser.ministry} />
</div>
</Text>
<Text size="xs" opacity={0.5}>
{task.completedByUser.email}
</Text>
<Text className="mt-2 italic" size="sm">
At: {formatDate(task.completedAt)}
</Text>
</div>
</>
)}
</Group>
<KeyValueTable data={task.closedMetadata || {}} />
</Table.Td>
</Table.Tr>
))
Expand All @@ -157,12 +124,10 @@ export default function TableBody({ data, assignees }: TableProps) {
<Table.Tr>
<Table.Th>Type</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th>Decision</Table.Th>
<Table.Th>Assigned Roles/Permissions</Table.Th>
<Table.Th>Assigned User(s)</Table.Th>
<Table.Th>Assigned</Table.Th>
<Table.Th>Completed</Table.Th>
<Table.Th>Data</Table.Th>
<Table.Th>Created At</Table.Th>
<Table.Th>Completed By</Table.Th>
<Table.Th>Metadata</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>{rows}</Table.Tbody>
Expand Down
Loading

0 comments on commit 411aa58

Please sign in to comment.