From 27ad515774d88d95c8919b1dd438bff7cd2ece4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malte=20Lauk=C3=B6tter?= Date: Wed, 13 Dec 2023 15:28:03 +0100 Subject: [PATCH] Add phase and task suggestion-logic --- app/components/TimeEntryForm.tsx | 268 ++++++++++++++++++++----------- 1 file changed, 178 insertions(+), 90 deletions(-) diff --git a/app/components/TimeEntryForm.tsx b/app/components/TimeEntryForm.tsx index 5b3f13c5..39bb8749 100644 --- a/app/components/TimeEntryForm.tsx +++ b/app/components/TimeEntryForm.tsx @@ -5,58 +5,14 @@ import { buttonBlue, buttonRed } from "~/utils/colors"; import { TrackyTask } from "~/tasks/useTasks"; import { usePhaseNames } from "~/tasks/usePhaseNames"; -/* -function onRecurringTaskChange(event) { - if (event.target.checked) { - descriptionSegments = [...descriptionSegments, event.target.id].filter( - (f) => f !== "" - ); - } else { - descriptionSegments = descriptionSegments.filter( - (segment) => segment !== event.target.id - ); - } -} - -function handleChipClick(phaseAndTask) { - if (!descriptionSegments.includes(phaseAndTask)) { - descriptionSegments = [...descriptionSegments, phaseAndTask].filter( - (f) => f !== "" - ); - } -} - -function removeChip(phaseAndTask) { - descriptionSegments = descriptionSegments.filter( - (segment) => segment !== phaseAndTask - ); -} - -function togglePhase(phase) { - phase.open = !phase.open; -} - -function openPhases() { - for (const phase of phases) { - for (const phaseTask of phaseTasks) { - if ( - descriptionSegments.includes([phaseTask.name, phase.name].join(" ")) - ) { - phase.open = true; - break; - } - } - } -}*/ - interface Props { values?: { hours: string; description: string; }; errors?: { - hours?: unknown; - description?: unknown; + hours?: string; + description?: string; }; addOrUpdateClicked: (hours: string, description: string) => unknown; deleteClicked?: () => unknown; @@ -69,6 +25,19 @@ interface Props { addMode?: boolean; } +function descriptionToSegmenets(description: string): string[] { + return description !== "" + ? description + .split(",") + .map((segment) => segment.trim()) + .filter((segment) => segment !== "") + : []; +} + +function segmentsToDescription(segments: string[]): string { + return segments.join(", "); +} + export function TimeEntryForm({ values = { hours: "", @@ -105,22 +74,57 @@ export function TimeEntryForm({ const minHeight = `${1 + minRows * 1.2}em`; const maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`; - const [descriptionSegments, setDescriptionSegments] = useState(() => - values.description !== "" ? values.description.split(", ") : [], - ); - const [description, setDescription] = useState(() => - descriptionSegments.join(", "), - ); + const [description, setDescription] = useState(() => values.description); + + const descriptionSegments = descriptionToSegmenets(description); const [hours, setHours] = useState(values.hours); const [updateMode, setUpdateMode] = useState(false); const phaseNames = usePhaseNames(position.id, position.subproject); - const phases = phaseNames.map((value) => ({ name: value, open: false })); - // todo: openPhases(); + const phases = phaseNames.map((value) => { + for (const phaseTask of phaseTasks) { + if (descriptionSegments.includes([phaseTask.name, value].join(" "))) { + return { name: value, open: true }; + } + } + return { name: value, open: false }; + }); + + function addDescriptionSegment(segment: string) { + setDescription((description) => + segmentsToDescription([...descriptionToSegmenets(description), segment]), + ); + } + + function removeDescriptionSegment(segment: string) { + setDescription((description) => { + const segments = descriptionToSegmenets(description); + return segmentsToDescription( + segments.filter((entry) => entry !== segment), + ); + }); + } + + function toggleDescriptionSegment(segment: string) { + if (description.includes(segment)) { + removeDescriptionSegment(segment); + } else { + addDescriptionSegment(segment); + } + } - useEffect(() => { - setDescription(descriptionSegments.join(", ")); - }, [descriptionSegments]); + function removeDuplicatedCommas(description: string) { + const segments = descriptionToSegmenets(description); + return segmentsToDescription( + segments.filter( + (entry, index) => entry !== "" || index === segments.length - 1, + ), + ); + } + + function hasDuplicatedCommas(description: string) { + return description.match(/\,\s*\,/g) !== null; + } function handleDescriptionChange( event: React.FormEvent, @@ -132,31 +136,11 @@ export function TimeEntryForm({ if (event.nativeEvent.inputType !== "insertLineBreak") { errors = {}; } - if ( - event.nativeEvent.inputType === "deleteContentBackward" && - description.charAt(description.length - 1) === " " - ) { - setDescription(event.target.value); - } else { - setDescription(event.target.value); - if (event.nativeEvent.data !== " ") { - setDescriptionSegments( - event.target.value.split(",").map((value) => value.trim()), - ); - } - } - const indexEmptyEntry = descriptionSegments.indexOf(""); - if ( - indexEmptyEntry !== -1 && - indexEmptyEntry !== descriptionSegments.length - 1 - ) { - setDescriptionSegments( - descriptionSegments.filter( - (entry, index) => - entry !== "" || index === descriptionSegments.length - 1, - ), - ); + let newDescription = event.target.value; + if (hasDuplicatedCommas(newDescription)) { + newDescription = removeDuplicatedCommas(newDescription); } + setDescription(newDescription); } else { console.error("handleDescriptionChange", event); // todo throw something @@ -194,12 +178,6 @@ export function TimeEntryForm({ setHours(hours); setDescription(description); setUpdateMode(false); - - // TODO noci: do like with handleAdd - // setTimeout(() => { - // updateMode = false; - // phases.forEach((phase) => (phase.open = false)); - // }, 2000); } } @@ -209,6 +187,22 @@ export function TimeEntryForm({ setUpdateMode(false); } + function onRecurringTaskChange(event: React.ChangeEvent) { + if (event.target.checked) { + addDescriptionSegment(event.target.id); + } else { + removeDescriptionSegment(event.target.id); + } + } + + function handleChipClick(phaseAndTask: string) { + toggleDescriptionSegment(phaseAndTask); + } + + function removeChip(phaseAndTask: string) { + removeDescriptionSegment(phaseAndTask); + } + return (
@@ -245,10 +239,104 @@ export function TimeEntryForm({ placeholder="2:15" />
-
+
+ {Object.values(errors).length > 0 && ( +
+
    + {Object.values(errors).map((error) => ( +
  • ⚠ {error}
  • + ))} +
+
+ )} +
+
+
+ {recurringTasks && phases && ( + <> + +
+ {recurringTasks.map((entry) => ( +
+ + +
+ ))} +
+ + )} +
+
+ {phases.map((phase) => ( +
+ + {phase.name} + + + + + {phaseTasks && ( +
+ {phaseTasks.map((task) => ( +
+ handleChipClick( + [task.name, phase.name].join(" "), + ) + } + > + {task.name} + {descriptionSegments.includes( + [task.name, phase.name].join(" "), + ) && ( + + )} +
+ ))} +
+ )} +
+ ))}
-
-