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