diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 566103a..08e31e9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,9 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/vite": "^4.1.11", + "@types/react-datepicker": "^6.2.0", "react": "^19.1.0", + "react-datepicker": "^8.4.0", "react-dom": "^19.1.0", "react-router-dom": "^7.7.0", "tailwindcss": "^4.1.11" @@ -611,6 +613,59 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.4.tgz", + "integrity": "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1534,12 +1589,22 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, + "node_modules/@types/react-datepicker": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.2.0.tgz", + "integrity": "sha512-+JtO4Fm97WLkJTH8j8/v3Ldh7JCNRwjMYjRaKh4KHH0M3jJoXtwiD3JBCsdlg3tsFIw9eQSqyAPeVDN2H2oM9Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.2", + "@types/react": "*", + "date-fns": "^3.3.1" + } + }, "node_modules/@types/react-dom": { "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", @@ -1952,6 +2017,15 @@ "node": ">=18" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2007,9 +2081,18 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -3166,6 +3249,46 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.4.0.tgz", + "integrity": "sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.3", + "clsx": "^2.1.1", + "date-fns": "^4.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/react-datepicker/node_modules/@floating-ui/react": { + "version": "0.27.13", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.13.tgz", + "integrity": "sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.4", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/react-datepicker/node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -3383,6 +3506,12 @@ "node": ">=8" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tailwindcss": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", diff --git a/frontend/package.json b/frontend/package.json index 79f0532..c4a1211 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,9 @@ }, "dependencies": { "@tailwindcss/vite": "^4.1.11", + "@types/react-datepicker": "^6.2.0", "react": "^19.1.0", + "react-datepicker": "^8.4.0", "react-dom": "^19.1.0", "react-router-dom": "^7.7.0", "tailwindcss": "^4.1.11" diff --git a/frontend/src/components/ChecklistItem.tsx b/frontend/src/components/ChecklistItem.tsx index f942dc2..2b599ee 100644 --- a/frontend/src/components/ChecklistItem.tsx +++ b/frontend/src/components/ChecklistItem.tsx @@ -192,7 +192,7 @@ export default function ChecklistItem({ return (
  • -
    +
    {/* Checkbox */}
    - {/* Dependency warning */} - {!item.checked && !canComplete && dependencyItems.length > 0 && ( -
    - ⚠️ Depends on: {dependencyItems.map(dep => dep?.content).join(', ')} -
    - )} - - {/* Date constraint warning */} - {!item.checked && !dateConstraintsMet && ( -
    - ⏰ Date constraint: { - notBeforeDate && now < notBeforeDate - ? `Cannot complete before ${notBeforeDate.toLocaleString()}` - : notAfterDate && now > notAfterDate - ? `Cannot complete after ${notAfterDate.toLocaleString()}` - : 'Date constraint not met' - } + {/* Constraint warnings - compact on mobile */} + {!item.checked && !canComplete && ( +
    + {/* Dependency warning */} + {dependencyItems.length > 0 && ( +
    + ⚠️ + Depends on: {dependencyItems.map(dep => dep?.content).join(', ')} + {dependencyItems.length} deps +
    + )} + + {/* Date constraint warning */} + {!dateConstraintsMet && ( +
    + + + {notBeforeDate && now < notBeforeDate + ? `Cannot complete before ${notBeforeDate.toLocaleString()}` + : notAfterDate && now > notAfterDate + ? `Cannot complete after ${notAfterDate.toLocaleString()}` + : 'Date constraint not met' + } + + Time locked +
    + )}
    )}
    {/* Actions */} -
    +
    {/* Dependency indicators */} {item.dependencies && item.dependencies.length > 0 && (
    @@ -299,29 +309,29 @@ export default function ChecklistItem({ <> diff --git a/frontend/src/components/DateConstraintManager.tsx b/frontend/src/components/DateConstraintManager.tsx index ba60a1d..324e741 100644 --- a/frontend/src/components/DateConstraintManager.tsx +++ b/frontend/src/components/DateConstraintManager.tsx @@ -1,5 +1,7 @@ import React, { useState } from 'react' +import DatePicker from 'react-datepicker' import type { ChecklistItem } from '../types' +import 'react-datepicker/dist/react-datepicker.css' interface DateConstraintManagerProps { item: ChecklistItem @@ -12,75 +14,158 @@ export const DateConstraintManager: React.FC = ({ onSave, onCancel }) => { - const [notBefore, setNotBefore] = useState( - item.not_before ? new Date(item.not_before).toISOString().slice(0, 16) : '' + const [notBefore, setNotBefore] = useState( + item.not_before ? new Date(item.not_before) : null ) - const [notAfter, setNotAfter] = useState( - item.not_after ? new Date(item.not_after).toISOString().slice(0, 16) : '' + const [notAfter, setNotAfter] = useState( + item.not_after ? new Date(item.not_after) : null ) const handleSave = () => { - const notBeforeDate = notBefore ? new Date(notBefore).toISOString() : undefined - const notAfterDate = notAfter ? new Date(notAfter).toISOString() : undefined + const notBeforeDate = notBefore ? notBefore.toISOString() : undefined + const notAfterDate = notAfter ? notAfter.toISOString() : undefined onSave(notBeforeDate, notAfterDate) } const handleClear = () => { - setNotBefore('') - setNotAfter('') + setNotBefore(null) + setNotAfter(null) } + const now = new Date() + return ( -
    -
    -

    Manage Date Constraints

    -

    +

    +
    +
    +

    Manage Date Constraints

    + +
    + +

    Set when this item can be completed. Leave empty to remove constraints.

    -
    +
    -
    -
    -
    + {/* Preview */} + {(notBefore || notAfter) && ( +
    +

    Preview:

    +
    + {notBefore && ( +
    ✅ Can complete after: {notBefore.toLocaleString()}
    + )} + {notAfter && ( +
    ✅ Can complete before: {notAfter.toLocaleString()}
    + )} + {notBefore && notAfter && ( +
    + Time window: {Math.round((notAfter.getTime() - notBefore.getTime()) / (1000 * 60 * 60 * 24))} days +
    + )} +
    +
    + )} + +
    -
    +
    diff --git a/frontend/src/index.css b/frontend/src/index.css index 15d863b..1752cbb 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,4 +3,163 @@ /* Remove list styling from checklist items */ li { list-style: none; +} + +/* Custom styles for react-datepicker */ +.react-datepicker { + font-family: inherit; + border: 1px solid #d1d5db; + border-radius: 0.5rem; + background-color: white; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.dark .react-datepicker { + background-color: #374151; + border-color: #4b5563; + color: #f9fafb; +} + +.react-datepicker__header { + background-color: #f3f4f6; + border-bottom: 1px solid #d1d5db; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.dark .react-datepicker__header { + background-color: #4b5563; + border-color: #6b7280; +} + +.react-datepicker__current-month { + color: #111827; + font-weight: 600; +} + +.dark .react-datepicker__current-month { + color: #f9fafb; +} + +.react-datepicker__day-name { + color: #6b7280; +} + +.dark .react-datepicker__day-name { + color: #d1d5db; +} + +.react-datepicker__day { + color: #111827; + border-radius: 0.375rem; +} + +.dark .react-datepicker__day { + color: #f9fafb; +} + +.react-datepicker__day:hover { + background-color: #e5e7eb; +} + +.dark .react-datepicker__day:hover { + background-color: #6b7280; +} + +.react-datepicker__day--selected { + background-color: #3b82f6; + color: white; +} + +.react-datepicker__day--keyboard-selected { + background-color: #3b82f6; + color: white; +} + +.react-datepicker__day--disabled { + color: #9ca3af; +} + +.dark .react-datepicker__day--disabled { + color: #6b7280; +} + +.react-datepicker__time-container { + border-left: 1px solid #d1d5db; +} + +.dark .react-datepicker__time-container { + border-color: #6b7280; +} + +.react-datepicker__time { + background-color: white; +} + +.dark .react-datepicker__time { + background-color: #374151; +} + +.react-datepicker__time-list-item { + color: #111827; +} + +.dark .react-datepicker__time-list-item { + color: #f9fafb; +} + +.react-datepicker__time-list-item:hover { + background-color: #e5e7eb; +} + +.dark .react-datepicker__time-list-item:hover { + background-color: #6b7280; +} + +.react-datepicker__time-list-item--selected { + background-color: #3b82f6; + color: white; +} + +.react-datepicker__navigation { + border: none; + background: none; + color: #6b7280; +} + +.dark .react-datepicker__navigation { + color: #d1d5db; +} + +.react-datepicker__navigation:hover { + background-color: #e5e7eb; + border-radius: 0.375rem; +} + +.dark .react-datepicker__navigation:hover { + background-color: #6b7280; +} + +.react-datepicker__close-icon { + background-color: transparent; + border: none; + color: #6b7280; + cursor: pointer; + padding: 0; + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); +} + +.dark .react-datepicker__close-icon { + color: #d1d5db; +} + +.react-datepicker__close-icon:hover { + color: #374151; +} + +.dark .react-datepicker__close-icon:hover { + color: #f9fafb; } \ No newline at end of file