Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/frontend/src/components/ApplicationTable.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
import { MemoryRouter } from 'react-router-dom';
import ApplicationTable from './ApplicationTable';
import type { ApplicationRow } from '@hooks/useApplications';
import {
Expand All @@ -9,7 +10,11 @@ import {
} from '@utils/applicationFilters';

function renderWithChakra(ui: React.ReactElement) {
return render(<ChakraProvider value={defaultSystem}>{ui}</ChakraProvider>);
return render(
<ChakraProvider value={defaultSystem}>
<MemoryRouter>{ui}</MemoryRouter>
</ChakraProvider>,
);
}

const mockApplications: ApplicationRow[] = [
Expand Down
165 changes: 132 additions & 33 deletions apps/frontend/src/components/ApplicationTable.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { useMemo } from 'react';
import { Table } from '@chakra-ui/react';
import { useEffect, useMemo, useState } from 'react';
import { Flex, Input, InputGroup, Spacer, Table } from '@chakra-ui/react';
import type { ApplicationRow } from '@hooks/useApplications';
import StatusPill, { StatusPillConfig, StatusVariant } from './StatusPill';
import {
compileApplicationFilterPredicate,
compileApplicationSearchPredicate,
EMPTY_APPLICATION_FILTERS,
normalizeDateToDay,
type ApplicationFilters,
} from '@utils/applicationFilters';
import apiClient from '@api/apiClient';
import { MdEdit } from 'react-icons/md';
import { useNavigate } from 'react-router-dom';

const COLUMNS = [
'Name',
Expand Down Expand Up @@ -67,6 +71,11 @@
searchQuery = '',
filters = EMPTY_APPLICATION_FILTERS,
}: ApplicationTableProps) {
const navigate = useNavigate();
const [actualStartDates, setActualStartDates] = useState<
Record<number, string>
>({});
const [editingIds, setEditingIds] = useState<Set<number>>(new Set());
const matchesStructuredFilters = useMemo(
() => compileApplicationFilterPredicate(filters),
[filters],
Expand All @@ -77,6 +86,19 @@
[searchQuery],
);

useEffect(() => {
setActualStartDates((prev) => {
const next = { ...prev };
applications.forEach((application) => {
if (next[application.appId] === undefined) {
next[application.appId] =
normalizeDateToDay(application.actualStartDate) ?? '';
}
});
return next;
});
}, [applications]);

const filteredApplications = useMemo(
() =>
applications.filter((application) => {
Expand All @@ -88,6 +110,19 @@
[applications, matchesSearchQuery, matchesStructuredFilters],
);

const handleActualStartDateUpdate = async (
nextDate: string,
application: ApplicationRow,
) => {
if (!application) return;
const updatedApplication = await apiClient.updateApplicationActualStartDate(

Check warning on line 118 in apps/frontend/src/components/ApplicationTable.tsx

View workflow job for this annotation

GitHub Actions / build

'updatedApplication' is assigned a value but never used
application.appId,
nextDate,
);
// setApplicationsState(updatedApplication); TODO
console.log('we clicked the button!');
};

return (
<Table.Root striped stickyHeader minW="900px">
<Table.Header>
Expand All @@ -104,38 +139,102 @@
</Table.Row>
</Table.Header>
<Table.Body>
{filteredApplications.map((application) => (
<Table.Row key={application.appId}>
<Table.Cell>
<a
href={`/admin/view-application/${application.appId}`}
aria-label={`View application ${application.appId}`}
style={{ color: '#0b5fff', textDecoration: 'underline' }}
{filteredApplications.map((application) => {
const isEditingActualStart = editingIds.has(application.appId);

return (
<Table.Row
key={application.appId}
onClick={() =>
navigate(`/admin/view-application/${application.appId}`)
}
transition="background-color 240ms ease-in-out"
_hover={{
backgroundColor: '#DBEAFE',
'& > td': {
backgroundColor: '#DBEAFE',
transition: 'background-color 240ms ease-in-out',
},
}}
>
<Table.Cell>{titleCaseName(application.name)}</Table.Cell>
<Table.Cell>
{formatDate(application.proposedStartDate)}
</Table.Cell>
<Table.Cell
onClick={(event) => {
if (isEditingActualStart) {
event.stopPropagation();
}
}}
>
{titleCaseName(application.name)}
</a>
</Table.Cell>
<Table.Cell>{formatDate(application.proposedStartDate)}</Table.Cell>
<Table.Cell>{formatDate(application.actualStartDate)}</Table.Cell>
<Table.Cell>
{formatDesiredExperience(application.desiredExperience)}
</Table.Cell>
<Table.Cell>{application.applicantType}</Table.Cell>
<Table.Cell>{application.discipline}</Table.Cell>
<Table.Cell>
{titleCaseName(application.disciplineAdminName)}
</Table.Cell>
<Table.Cell>
{StatusPillConfig[application.status as StatusVariant] ? (
<StatusPill variant={application.status as StatusVariant}>
{StatusPillConfig[application.status as StatusVariant].label}
</StatusPill>
) : (
application.status
)}
</Table.Cell>
</Table.Row>
))}
<Flex align="center">
{isEditingActualStart ? (
<InputGroup width="115px" flex="1">
<Input
type="date"
size="xs"
value={actualStartDates[application.appId] ?? ''}
onChange={(event) => {
event.stopPropagation();
const value = event.target.value;
setActualStartDates((prev) => ({
...prev,
[application.appId]: value,
}));
}}
/>
</InputGroup>
) : (
formatDate(actualStartDates[application.appId])
)}
<Spacer />
<MdEdit
onClick={(event) => {
event.stopPropagation();
if (editingIds.has(application.appId)) {
handleActualStartDateUpdate(
actualStartDates[application.appId] ?? '',
application,
);

setEditingIds((prev) => {
const next = new Set(prev);
next.delete(application.appId);
return next;
});
} else {
setEditingIds((prev) =>
new Set(prev).add(application.appId),
);
}
}}
/>
</Flex>
</Table.Cell>
<Table.Cell>
{formatDesiredExperience(application.desiredExperience)}
</Table.Cell>
<Table.Cell>{application.applicantType}</Table.Cell>
<Table.Cell>{application.discipline}</Table.Cell>
<Table.Cell>
{titleCaseName(application.disciplineAdminName)}
</Table.Cell>
<Table.Cell>
{StatusPillConfig[application.status as StatusVariant] ? (
<StatusPill variant={application.status as StatusVariant}>
{
StatusPillConfig[application.status as StatusVariant]
.label
}
</StatusPill>
) : (
application.status
)}
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table.Root>
);
Expand Down
Loading