Skip to content

Commit

Permalink
Fix page crash when entry with archived task is rendered
Browse files Browse the repository at this point in the history
- Show archived task on entry grouping and task selection
- Enable noUncheckedIndexedAccess in tsconfig
  • Loading branch information
tnagorra committed Jan 27, 2025
1 parent 3eb21ba commit aefa5d0
Show file tree
Hide file tree
Showing 17 changed files with 175 additions and 56 deletions.
2 changes: 1 addition & 1 deletion backend
Submodule backend updated 132 files
4 changes: 2 additions & 2 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import styles from './styles.module.css';

type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'transparent' | 'dropdown-item';

const buttonVariantToClassNameMap: Record<ButtonVariant, string> = {
const buttonVariantToClassNameMap: Record<ButtonVariant, string | undefined> = {
primary: styles.primary,
secondary: styles.secondary,
tertiary: styles.tertiary,
Expand All @@ -24,7 +24,7 @@ const buttonVariantToClassNameMap: Record<ButtonVariant, string> = {
'dropdown-item': styles.dropdownItem,
};

const spacingTypeToClassNameMap: Record<SpacingType, string> = {
const spacingTypeToClassNameMap: Record<SpacingType, string | undefined> = {
none: styles.noSpacing,
'2xs': styles.condensedSpacing,
xs: styles.compactSpacing,
Expand Down
2 changes: 1 addition & 1 deletion src/components/SearchSelectInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type Props<
searchOptions?: OPTION[] | undefined | null;
keySelector: (option: OPTION) => OPTION_KEY;
labelSelector: (option: OPTION) => string;
colorSelector?: (option: OPTION, index: number) => [string, string];
colorSelector?: (option: OPTION, index: number) => readonly [string, string];
descriptionSelector?: (option: OPTION) => string;
hideOptionFilter?: (option: OPTION) => boolean;
name: NAME;
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function getNewKey<T, Q extends OptionKey>(

const newIndex = modulo(oldIndex + increment, options.length);

return keySelector(options[newIndex], newIndex);
return keySelector(options[newIndex] as T, newIndex);
}

function useKeyboard<T, Q extends OptionKey>(
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useSpacingTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ function useSpacingTokens(props: Props) {
: spacingVariantToTokenStartIndex[variant];
const offset = spacingTypeToOffsetMap[spacing];

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
spacingValue = spacingTokens[
bound(
startIndex + offset,
0,
spacingTokens.length - 1,
)
];
]!;
}

if (isNotDefined(spacing)) {
Expand Down
35 changes: 26 additions & 9 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export function getDurationNumber(value: string | undefined) {
// :m
if (value.match(/^\d{0,2}:\d{1,2}$/)) {
const [hourStr, minuteStr] = value.split(':');
if (isNotDefined(hourStr) || isNotDefined(minuteStr)) {
return null;
}
return validateHhmm(hourStr, minuteStr) ?? null;
}
// hhmm
Expand Down Expand Up @@ -160,6 +163,7 @@ export function getChangedItems<T>(
keySelector: (item: T) => string,
) {
const initialKeysMap = listToMap(initialItems ?? [], keySelector);

const finalKeysMap = listToMap(finalItems ?? [], keySelector);

const addedKeys = Object.keys(finalKeysMap).filter(
Expand All @@ -170,13 +174,15 @@ export function getChangedItems<T>(
);
const updatedKeys = Object.keys(initialKeysMap).filter(
(key) => {
if (isNotDefined(finalKeysMap[key])) {
// This should be safe as we are using keys from Object.keys(initialKeysMap)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const initialObj = initialKeysMap[key]!;
const finalObj = finalKeysMap[key];

if (isNotDefined(finalObj)) {
return false;
}

const initialObj = initialKeysMap[key];
const finalObj = finalKeysMap[key];

const initialJson = JSON.stringify(
initialObj,
initialObj ? Object.keys(initialObj).sort() : undefined,
Expand All @@ -191,9 +197,16 @@ export function getChangedItems<T>(
);

return {
addedItems: addedKeys.map((key) => finalKeysMap[key]),
removedItems: removedKeys.map((key) => initialKeysMap[key]),
updatedItems: updatedKeys.map((key) => finalKeysMap[key]),
// NOTE: This should be safe as addedKeys is subset of Object.keys(finalKeysMap)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
addedItems: addedKeys.map((key) => finalKeysMap[key]!),
// NOTE: This should be safe as removedKeys is subset of Object.keys(initialKeysMap)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
removedItems: removedKeys.map((key) => initialKeysMap[key]!),
// NOTE: This should be safe as updatedKeys is subset of
// Object.keys(initialKeysMap) intersection Object.keys(finalKeysMap)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
updatedItems: updatedKeys.map((key) => finalKeysMap[key]!),
};
}

Expand Down Expand Up @@ -234,7 +247,9 @@ export function sortByAttributes<LIST_ITEM, ATTRIBUTE>(
const currentSortResult = sortFn(
a,
b,
attributes[i],
// NOTE: This should be safe as we are iterating over atttributes
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
attributes[i]!,
);

if (currentSortResult !== 0) {
Expand Down Expand Up @@ -296,7 +311,9 @@ export function groupListByAttributes<LIST_ITEM, ATTRIBUTE>(
];
}

const prevListItem = list[listIndex - 1];
// NOTE: This will always be in-bounds because we have already checked for listIndex === 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const prevListItem = list[listIndex - 1]!;
const attributeMismatchIndex = attributes.findIndex((attribute) => {
const hasSameCurrentAttribute = compareItemAttributes(
listItem,
Expand Down
4 changes: 2 additions & 2 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const defaultConfigValue: ConfigStorage = {
collapsedGroups: [],
};

export const colorscheme: [string, string][] = [
export const colorscheme = [
// gray 0
['#454447', '#eaeaea'],
// idigo 1
Expand All @@ -46,7 +46,7 @@ export const colorscheme: [string, string][] = [
['#a86e00', '#fde3aa'],
// horchata 8
['#7d5327', '#ecdecc'],
];
] as const satisfies readonly (readonly [string, string])[];

// FIXME: We should instead generate these options
export const numericOptions: NumericOption[] = [
Expand Down
23 changes: 15 additions & 8 deletions src/utils/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,22 @@ export function unwrapRoute<K extends object>(
);

wrappedRoutes.forEach((route) => {
if (route.parent) {
const parentId = route.parent.id;
if (!route.parent) {
return;
}
const parentId = route.parent.id;

const parentRoute = mapping[parentId];
if (!parentRoute) {
// eslint-disable-next-line no-console
console.error('Parent route is not defined');
return;
}

const parentRoute = mapping[parentId];
if (parentRoute.children) {
parentRoute.children.push(route);
} else {
parentRoute.children = [route];
}
if (parentRoute.children) {
parentRoute.children.push(route);
} else {
parentRoute.children = [route];
}
});

Expand Down
25 changes: 20 additions & 5 deletions src/views/DailyJournal/DayView/WorkItemRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {
_cs,
isDefined,
unique,
} from '@togglecorp/fujs';

import Button from '#components/Button';
Expand Down Expand Up @@ -62,7 +63,7 @@ function workItemStatusKeySelector(item: WorkItemStatusOption) {
function workItemStatusLabelSelector(item: WorkItemStatusOption) {
return item.label;
}
function workItemStatusColorSelector(item: WorkItemStatusOption): [string, string] {
function workItemStatusColorSelector(item: WorkItemStatusOption): readonly [string, string] {
if (item.key === 'DOING') {
return colorscheme[1];
}
Expand All @@ -72,13 +73,16 @@ function workItemStatusColorSelector(item: WorkItemStatusOption): [string, strin
return colorscheme[7];
}

function defaultColorSelector<T>(_: T, i: number): [string, string] {
return colorscheme[i % colorscheme.length];
function defaultColorSelector<T>(_: T, i: number): readonly [string, string] {
// NOTE: This is safe as we the index is bounded
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return colorscheme[i % colorscheme.length]!;
}

interface Props {
className?: string;
workItem: WorkItem;
tasks: Task[] | undefined;
contractId: string | undefined;

onClone?: (clientId: string, override?: Partial<WorkItem>) => void;
Expand All @@ -90,6 +94,7 @@ function WorkItemRow(props: Props) {
const {
className,
workItem,
tasks,
contractId,
onClone,
onDelete,
Expand All @@ -112,8 +117,18 @@ function WorkItemRow(props: Props) {
);

const filteredTaskList = useMemo(
() => enums?.private?.allActiveTasks?.filter((task) => task.contract.id === contractId),
[contractId, enums],
() => (
unique(
[
...enums?.private?.allActiveTasks ?? [],
...tasks ?? [],
],
(item) => item.id,
).filter(
(task) => task.contract.id === contractId,
)
),
[contractId, enums, tasks],
);

const handleStatusCheck = useCallback(() => {
Expand Down
40 changes: 31 additions & 9 deletions src/views/DailyJournal/DayView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
compareString,
isDefined,
isNotDefined,
listToMap,
sum,
} from '@togglecorp/fujs';

Expand All @@ -32,6 +33,7 @@ import {
import {
DailyJournalAttribute,
EntriesAsList,
Task,
WorkItem,
} from '#utils/types';

Expand All @@ -52,6 +54,7 @@ const dateFormatter = new Intl.DateTimeFormat(
interface Props {
className?: string;
workItems: WorkItem[] | undefined;
tasks: Task[] | undefined;
loading: boolean;
errored: boolean;
onWorkItemClone: (clientId: string, override?: Partial<WorkItem>) => void;
Expand All @@ -70,9 +73,25 @@ function DayView(props: Props) {
loading,
errored,
selectedDate,
tasks,
} = props;

const { taskById } = useContext(EnumsContext);
// FIXME: We should still get archived tasks here
const { taskById: oldTaskById } = useContext(EnumsContext);

// FIXME: memoize this
const newTaskById = listToMap(
tasks,
(item) => item.id,
);

const taskById = useMemo(
() => ({
...oldTaskById,
...newTaskById,
}),
[oldTaskById, newTaskById],
);

const [
storedConfig,
Expand Down Expand Up @@ -126,15 +145,15 @@ function DayView(props: Props) {
const taskDetails = taskById[item.task];

if (attr.key === 'task') {
return taskDetails.name;
return taskDetails?.name ?? 'Unknown Task';
}

if (attr.key === 'contract') {
return taskDetails.contract.name;
return taskDetails?.contract.name ?? 'Unknown Contract';
}

if (attr.key === 'project') {
return taskDetails.contract.project.name;
return taskDetails?.contract.project.name ?? 'Unknown Project';
}

return undefined;
Expand All @@ -150,6 +169,10 @@ function DayView(props: Props) {

const taskDetails = taskById[item.task];

if (!taskDetails) {
return undefined;
}

if (attr.key === 'project') {
return taskDetails.contract.project.logo;
}
Expand Down Expand Up @@ -364,10 +387,6 @@ function DayView(props: Props) {
return null;
}

const taskDetails = taskById?.[groupedItem.value.task];
if (!taskDetails) {
return null;
}
const hidden = enableCollapsibleGroups
&& collapsedGroups.some(
(groupKey) => groupedItem.itemKey.startsWith(groupKey),
Expand All @@ -381,6 +400,8 @@ function DayView(props: Props) {
|| isNotDefined(groupedItem.value.duration)
);

const taskDetails = taskById?.[groupedItem.value.task];

return (
<div
className={styles.workItemContainer}
Expand All @@ -394,10 +415,11 @@ function DayView(props: Props) {
<WorkItemRow
className={_cs(styles.workItem, itemErrored && styles.errored)}
workItem={groupedItem.value}
tasks={tasks}
onClone={onWorkItemClone}
onChange={onWorkItemChange}
onDelete={onWorkItemDelete}
contractId={taskDetails.contract.id}
contractId={taskDetails?.contract.id}
/>
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion src/views/DailyJournal/EndSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ function EndSidebar(props: Props) {
(task) => task.contract.project.id,
undefined,
(list) => ({
project: list[0].contract.project,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
project: list[0]!.contract.project,
workItems: list,
}),
),
Expand Down
4 changes: 3 additions & 1 deletion src/views/DailyJournal/StartSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ function StartSidebar(props: Props) {
}

const [removedItem] = newAttributes.splice(sourceIndex, 1);
newAttributes.splice(destinationIndex, 0, removedItem);
// NOTE: We can assert removedItem is not undefined as sourceIndex is already checked for -1
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
newAttributes.splice(destinationIndex, 0, removedItem!);

setConfigFieldValue(newAttributes, 'dailyJournalAttributeOrder');
}, [setConfigFieldValue, storedConfig.dailyJournalAttributeOrder]);
Expand Down
Loading

0 comments on commit aefa5d0

Please sign in to comment.