Skip to content

Commit

Permalink
Add grouping of todos (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
vikingair authored Oct 10, 2024
1 parent c4ad85e commit f1e8b84
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 53 deletions.
7 changes: 2 additions & 5 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@ import ts from "typescript-eslint";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
const compat = new FlatCompat({ baseDirectory: __dirname });

/**
* source: https://github.com/import-js/eslint-plugin-import/issues/2948#issuecomment-2148832701
* @param {string} name the pugin name
* @param {string} alias the plugin alias
* @returns {import("eslint").ESLint.Plugin}
* @returns {import("typescript-eslint").plugin}
*/
function legacyPlugin(name, alias = name) {
const plugin = compat.plugins(name)[0]?.plugins?.[alias];
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/assets/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@
--color-warn: lightcoral;
--color-info-rgb: 173, 216, 230;
--color-info: rgba(var(--color-info-rgb));
--color-group: hsl(278.42deg 60% 37%);
--color-group-details: hsl(278deg 60% 37% / 50%);
--color-success: lightgreen;
}
8 changes: 4 additions & 4 deletions frontend/src/assets/components/Main.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
main {
@media screen and (min-width: 1000px) {
height: calc(100vh - 7em);
}

display: flex;
flex-direction: column;
gap: 2em;

@media screen and (min-width: 1000px) {
height: calc(100vh - 7em);
}

h2 {
display: flex;
justify-content: space-between;
Expand Down
24 changes: 21 additions & 3 deletions frontend/src/assets/components/Todos.scss
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
.todos {
display: flex;
flex-direction: column;
gap: 0.5em;

.add-todo {
display: flex;
align-items: center;
gap: 0.5em;
margin-bottom: 1em;
margin-bottom: 0.5em;

form, input {
width: 100%;
}
}

> details {
margin-top: 2em;
> .todos_resolved {
margin-top: 0.5em;

summary {
color: var(--color-success);
}
}

.todos_resolved_items {
display: flex;
flex-direction: column;
gap: 0.5em;
}

.todos_group {
background-color: var(--color-group-details);

> summary {
background-color: var(--color-group);
}
}

&__list {
display: flex;
flex-direction: column;
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/assets/components/Tracks.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
.tracks {
@media screen and (min-width: 1000px) {
overflow: auto;
}

flex-grow: 1;
display: grid;
grid-auto-rows: min-content;
Expand All @@ -11,6 +7,10 @@
grid-row-gap: 0.5em;
align-items: center;

@media screen and (min-width: 1000px) {
overflow: auto;
}

.track {
&__rate svg {
width: 2em;
Expand Down
107 changes: 72 additions & 35 deletions frontend/src/ui/Todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,47 @@ import { Checkbox } from "./base/Checkbox";
import { EditableInput } from "./forms/EditableInput";
import { EditableTextarea } from "./forms/EditableTextarea";
import { SingleInputForm } from "./forms/SingleInputForm";
import { getTagAndTextForDescription } from "./TrackDescriptionText";

type NestedTodos = { list: Todo[]; nested: Record<string, Todo[]> };

const convertListToNested = (list: Todo[]): NestedTodos =>
list.reduce<NestedTodos>(
(red, cur) => {
const [tag] = getTagAndTextForDescription(cur.title);
if (!tag) red.list = [...red.list, cur];
else {
red.nested[tag] ??= [];
red.nested[tag] = [...red.nested[tag], cur];
}
return red;
},
{ list: [], nested: {} },
);

type TodoItemsProps = {
todos: Todo[];
onChange: (todo: Todo) => Promise<void>;
onRemove: (todo: Todo) => Promise<void>;
onMove: (todoID: string, index: number) => Promise<void>;
noTag?: boolean;
};

const TodoItems: React.FC<TodoItemsProps> = ({ todos, ...rest }) => (
<div className="todos__list">
{todos.map((todo, index) => (
<TodoItem todo={todo} key={todo.ID} index={index} {...rest} />
))}
</div>
);

type TodoItemProps = {
todo: Todo;
index: number;
onChange: (todo: Todo) => Promise<void>;
onRemove: (todo: Todo) => Promise<void>;
onMove?: (todoID: string, index: number) => Promise<void>;
onMove: (todoID: string, index: number) => Promise<void>;
noTag?: boolean;
};

const TodoItem: React.FC<TodoItemProps> = ({
Expand All @@ -29,6 +63,7 @@ const TodoItem: React.FC<TodoItemProps> = ({
onRemove,
index,
onMove,
noTag,
}) => {
const onChangeTitle = useCallback(
(title: string) => onChange({ ...todo, title }),
Expand Down Expand Up @@ -93,6 +128,7 @@ const TodoItem: React.FC<TodoItemProps> = ({
onChange={onChangeTitle}
inputName={"todo-title"}
className={"todo__sub-summary"}
hideTag={noTag}
/>
<button
onClick={_onRemove}
Expand Down Expand Up @@ -160,52 +196,53 @@ export const Todos: React.FC = () => {
[],
);

const [openTodos, resolvedTodos] = useMemo<[Todo[], Todo[]]>(
const [openTodos, resolvedTodos] = useMemo<NestedTodos[]>(
() =>
todos.reduce(
(red, todo) => {
if (todo.resolvedAt) {
red[1].push(todo);
} else {
red[0].push(todo);
}
return red;
},
[[], []] as [Todo[], Todo[]],
),
todos
.reduce<[Todo[], Todo[]]>(
(red, todo) => {
if (todo.resolvedAt) {
red[1].push(todo);
} else {
red[0].push(todo);
}
return red;
},
[[], []],
)
.map(convertListToNested),
[todos],
);

const handlers = {
onChange: createOrUpdateTodo,
onRemove: removeTodo,
onMove: moveTodo,
};

return (
<div className="todos">
<div className="add-todo">
<IconPlus />
<SingleInputForm onChange={add} inputName={"toto-title"} />
</div>
<div className="todos__list">
{openTodos.map((todo, index) => (
<TodoItem
todo={todo}
key={todo.ID}
index={index}
onChange={createOrUpdateTodo}
onRemove={removeTodo}
onMove={moveTodo}
/>
))}
</div>
<details>
{Object.entries(openTodos.nested).map(([tag, todos]) => (
<details className="todos_group" key={tag}>
<summary>{tag}</summary>
<TodoItems todos={todos} {...handlers} noTag />
</details>
))}
<TodoItems todos={openTodos.list} {...handlers} />
<details className="todos_resolved">
<summary>Resolved ✔</summary>
<div className="todos__list">
{resolvedTodos.map((todo, index) => (
<TodoItem
todo={todo}
key={todo.ID}
onChange={createOrUpdateTodo}
onRemove={removeTodo}
index={index}
/>
<div className="todos_resolved_items">
{Object.entries(resolvedTodos.nested).map(([tag, todos]) => (
<details className="todos_group" key={tag}>
<summary>{tag}</summary>
<TodoItems todos={todos} {...handlers} noTag />
</details>
))}
<TodoItems todos={resolvedTodos.list} {...handlers} />
</div>
</details>
</div>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/ui/TrackDescriptionText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ export const getTagAndTextForDescription = (

export type TrackDescriptionTextProps = {
value: string;
hideTag?: boolean;
};

export const TrackDescriptionText: React.FC<TrackDescriptionTextProps> = ({
value,
hideTag,
}) => {
const [tag, text] = getTagAndTextForDescription(value);
return (
<em>
{tag && <span>{tag}</span>}
{!hideTag && tag && <span>{tag}</span>}
<span>{text}</span>
</em>
);
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/ui/forms/EditableInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type EditableInputProps = {
title?: string;
className?: string;
inputName: string;
hideTag?: boolean;
};

export const EditableInput: React.FC<EditableInputProps> = ({
Expand All @@ -19,6 +20,7 @@ export const EditableInput: React.FC<EditableInputProps> = ({
title,
className,
inputName,
hideTag,
}) => {
const [isEditing, setIsEditing] = useState(false);
const onEdit = useCallback(() => setIsEditing(true), []);
Expand All @@ -38,7 +40,7 @@ export const EditableInput: React.FC<EditableInputProps> = ({
/>
) : (
<>
<TrackDescriptionText value={value} />
<TrackDescriptionText value={value} hideTag={hideTag} />
<button
onClick={onEdit}
className={"icon-button"}
Expand Down

0 comments on commit f1e8b84

Please sign in to comment.