From 37bd4d1349acf181be2bff23667401b20a4d863f Mon Sep 17 00:00:00 2001 From: Steven Correia <45617825+StevenJCorreia@users.noreply.github.com> Date: Fri, 5 Sep 2025 07:55:20 -0400 Subject: [PATCH] feat: Restore toggleable due dates (#1332) --- .../components/cards/Card/ProjectContent.jsx | 7 +++- .../cards/CardModal/ProjectContent.jsx | 37 ++++++++++++++----- .../CardModal/ProjectContent.module.scss | 6 +++ .../cards/DueDateChip/DueDateChip.jsx | 26 ++++++++++--- .../cards/DueDateChip/DueDateChip.module.scss | 5 +++ client/src/models/Card.js | 12 ++++++ server/api/controllers/cards/create.js | 5 +++ server/api/controllers/cards/update.js | 6 +++ .../api/helpers/boards/import-from-trello.js | 2 +- server/api/helpers/cards/create-one.js | 8 ++++ server/api/helpers/cards/duplicate-one.js | 1 + server/api/helpers/cards/update-one.js | 14 +++++++ server/api/models/Card.js | 5 +++ ...0905101408_restore_toggleable_due_dates.js | 23 ++++++++++++ 14 files changed, 140 insertions(+), 17 deletions(-) create mode 100644 server/db/migrations/20250905101408_restore_toggleable_due_dates.js diff --git a/client/src/components/cards/Card/ProjectContent.jsx b/client/src/components/cards/Card/ProjectContent.jsx index 952acc86..267549f0 100644 --- a/client/src/components/cards/Card/ProjectContent.jsx +++ b/client/src/components/cards/Card/ProjectContent.jsx @@ -187,7 +187,12 @@ const ProjectContent = React.memo(({ cardId }) => { )} {card.dueDate && ( - + )} {card.stopwatch && ( diff --git a/client/src/components/cards/CardModal/ProjectContent.jsx b/client/src/components/cards/CardModal/ProjectContent.jsx index 1fd7024e..668a0a19 100644 --- a/client/src/components/cards/CardModal/ProjectContent.jsx +++ b/client/src/components/cards/CardModal/ProjectContent.jsx @@ -7,7 +7,7 @@ import React, { useCallback, useContext, useMemo, useState } from 'react'; import classNames from 'classnames'; import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; -import { Button, Grid, Icon } from 'semantic-ui-react'; +import { Button, Checkbox, Grid, Icon } from 'semantic-ui-react'; import { useDidUpdate } from '../../../lib/hooks'; import selectors from '../../../selectors'; @@ -175,6 +175,14 @@ const ProjectContent = React.memo(() => { [dispatch], ); + const handleDueCompletionChange = useCallback(() => { + dispatch( + entryActions.updateCurrentCard({ + isDueCompleted: !card.isDueCompleted, + }), + ); + }, [card.isDueCompleted, dispatch]); + const handleToggleStopwatchClick = useCallback(() => { dispatch( entryActions.updateCurrentCard({ @@ -410,19 +418,30 @@ const ProjectContent = React.memo(() => { context: 'title', })} - + {canEditDueDate ? ( - - - + <> + {!card.isClosed && ( + + )} + + + + ) : ( )} diff --git a/client/src/components/cards/CardModal/ProjectContent.module.scss b/client/src/components/cards/CardModal/ProjectContent.module.scss index 63d442b9..8bdc3890 100644 --- a/client/src/components/cards/CardModal/ProjectContent.module.scss +++ b/client/src/components/cards/CardModal/ProjectContent.module.scss @@ -60,6 +60,12 @@ vertical-align: top; } + .attachmentDueDate { + align-items: center; + display: flex; + gap: 4px; + } + .attachments { display: inline-block; margin: 0 8px 8px 0; diff --git a/client/src/components/cards/DueDateChip/DueDateChip.jsx b/client/src/components/cards/DueDateChip/DueDateChip.jsx index bff0aa3a..6d9ce31b 100644 --- a/client/src/components/cards/DueDateChip/DueDateChip.jsx +++ b/client/src/components/cards/DueDateChip/DueDateChip.jsx @@ -24,6 +24,7 @@ const Sizes = { const Statuses = { DUE_SOON: 'dueSoon', OVERDUE: 'overdue', + COMPLETED: 'completed', }; const LONG_DATE_FORMAT_BY_SIZE = { @@ -47,9 +48,17 @@ const STATUS_ICON_PROPS_BY_STATUS = { name: 'hourglass end', color: 'red', }, + [Statuses.COMPLETED]: { + name: 'checkmark', + color: 'green', + }, }; -const getStatus = (date) => { +const getStatus = (date, isCompleted) => { + if (isCompleted) { + return Statuses.COMPLETED; + } + const secondsLeft = Math.floor((date.getTime() - new Date().getTime()) / 1000); if (secondsLeft <= 0) { @@ -64,12 +73,12 @@ const getStatus = (date) => { }; const DueDateChip = React.memo( - ({ value, size, isDisabled, withStatus, withStatusIcon, onClick }) => { + ({ value, size, isCompleted, isDisabled, withStatus, withStatusIcon, onClick }) => { const [t] = useTranslation(); const forceUpdate = useForceUpdate(); const statusRef = useRef(null); - statusRef.current = withStatus ? getStatus(value) : null; + statusRef.current = withStatus ? getStatus(value, isCompleted) : null; const intervalRef = useRef(null); @@ -80,9 +89,13 @@ const DueDateChip = React.memo( ); useEffect(() => { - if (withStatus && statusRef.current !== Statuses.OVERDUE) { + if ( + withStatus && + statusRef.current !== Statuses.OVERDUE && + statusRef.current !== Statuses.COMPLETED + ) { intervalRef.current = setInterval(() => { - const status = getStatus(value); + const status = getStatus(value, isCompleted); if (status !== statusRef.current) { forceUpdate(); @@ -99,7 +112,7 @@ const DueDateChip = React.memo( clearInterval(intervalRef.current); } }; - }, [value, withStatus, forceUpdate]); + }, [value, isCompleted, withStatus, forceUpdate]); const contentNode = ( { + await knex.schema.alterTable('card', (table) => { + /* Columns */ + + table.boolean('is_due_completed'); + }); + + return knex('card') + .update({ + isDueCompleted: false, + }) + .whereNotNull('due_date'); +}; + +exports.down = (knex) => + knex.schema.table('card', (table) => { + table.dropColumn('is_due_completed'); + });