ooookay
This commit is contained in:
parent
4ed9538a93
commit
c7eae58857
5 changed files with 446 additions and 61 deletions
133
frontend/package-lock.json
generated
133
frontend/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -192,7 +192,7 @@ export default function ChecklistItem({
|
|||
|
||||
return (
|
||||
<li className="group list-none" style={{ listStyle: 'none' }}>
|
||||
<div className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-all duration-200 border border-transparent hover:border-gray-200 dark:hover:border-gray-700">
|
||||
<div className="flex items-start gap-2 sm:gap-3 p-2 sm:p-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-all duration-200 border border-transparent hover:border-gray-200 dark:hover:border-gray-700">
|
||||
{/* Checkbox */}
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
<input
|
||||
|
@ -200,7 +200,7 @@ export default function ChecklistItem({
|
|||
checked={item.checked}
|
||||
onChange={handleToggleCheck}
|
||||
disabled={Boolean(isLocked && !isLockedByMe) || (!item.checked && !canComplete)}
|
||||
className={`w-5 h-5 text-blue-600 bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 hover:border-blue-400 dark:hover:border-blue-500 ${
|
||||
className={`w-4 h-4 sm:w-5 sm:h-5 text-blue-600 bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-600 rounded-md focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 hover:border-blue-400 dark:hover:border-blue-500 ${
|
||||
!item.checked && !canComplete ? 'cursor-not-allowed' : ''
|
||||
}`}
|
||||
title={!item.checked && !canComplete ? 'Complete dependencies first' : ''}
|
||||
|
@ -213,7 +213,7 @@ export default function ChecklistItem({
|
|||
ref={contentRef}
|
||||
contentEditable={isEditing}
|
||||
suppressContentEditableWarning={true}
|
||||
className={`block w-full text-base leading-relaxed break-words outline-none transition-all duration-200 ${
|
||||
className={`block w-full text-sm sm:text-base leading-relaxed break-words outline-none transition-all duration-200 ${
|
||||
isEditing
|
||||
? 'px-3 py-2 border-2 border-blue-500 rounded-lg focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 bg-white dark:bg-gray-800 shadow-sm'
|
||||
: 'cursor-pointer hover:text-blue-600 dark:hover:text-blue-400'
|
||||
|
@ -231,29 +231,39 @@ export default function ChecklistItem({
|
|||
{item.content}
|
||||
</span>
|
||||
|
||||
{/* Dependency warning */}
|
||||
{!item.checked && !canComplete && dependencyItems.length > 0 && (
|
||||
<div className="mt-1 text-xs text-orange-600 dark:text-orange-400">
|
||||
⚠️ Depends on: {dependencyItems.map(dep => dep?.content).join(', ')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Date constraint warning */}
|
||||
{!item.checked && !dateConstraintsMet && (
|
||||
<div className="mt-1 text-xs text-red-600 dark:text-red-400">
|
||||
⏰ 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 && (
|
||||
<div className="mt-1 flex flex-wrap gap-1">
|
||||
{/* Dependency warning */}
|
||||
{dependencyItems.length > 0 && (
|
||||
<div className="inline-flex items-center gap-1 text-xs text-orange-600 dark:text-orange-400 bg-orange-50 dark:bg-orange-900/20 px-2 py-0.5 rounded-full border border-orange-200 dark:border-orange-800">
|
||||
<span className="text-xs">⚠️</span>
|
||||
<span className="hidden sm:inline">Depends on: {dependencyItems.map(dep => dep?.content).join(', ')}</span>
|
||||
<span className="sm:hidden">{dependencyItems.length} deps</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Date constraint warning */}
|
||||
{!dateConstraintsMet && (
|
||||
<div className="inline-flex items-center gap-1 text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 px-2 py-0.5 rounded-full border border-red-200 dark:border-red-800">
|
||||
<span className="text-xs">⏰</span>
|
||||
<span className="hidden sm:inline">
|
||||
{notBeforeDate && now < notBeforeDate
|
||||
? `Cannot complete before ${notBeforeDate.toLocaleString()}`
|
||||
: notAfterDate && now > notAfterDate
|
||||
? `Cannot complete after ${notAfterDate.toLocaleString()}`
|
||||
: 'Date constraint not met'
|
||||
}
|
||||
</span>
|
||||
<span className="sm:hidden">Time locked</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
||||
<div className="flex items-center gap-2 flex-shrink-0 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity duration-200">
|
||||
{/* Dependency indicators */}
|
||||
{item.dependencies && item.dependencies.length > 0 && (
|
||||
<div className="flex items-center gap-1">
|
||||
|
@ -299,29 +309,29 @@ export default function ChecklistItem({
|
|||
<>
|
||||
<button
|
||||
onClick={() => setIsDependencyModalOpen(true)}
|
||||
className="p-1.5 text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-all duration-200 group/deps"
|
||||
className="p-1 sm:p-1.5 text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-md transition-all duration-200 group/deps"
|
||||
title="Manage dependencies"
|
||||
>
|
||||
<svg className="w-4 h-4 group-hover/deps:scale-110 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-3 h-3 sm:w-4 sm:h-4 group-hover/deps:scale-110 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDateConstraintModalOpen(true)}
|
||||
className="p-1.5 text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded-md transition-all duration-200 group/date"
|
||||
className="p-1 sm:p-1.5 text-gray-400 hover:text-purple-600 dark:hover:text-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 rounded-md transition-all duration-200 group/date"
|
||||
title="Manage date constraints"
|
||||
>
|
||||
<svg className="w-4 h-4 group-hover/date:scale-110 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-3 h-3 sm:w-4 sm:h-4 group-hover/date:scale-110 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
className="p-1.5 text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-all duration-200 disabled:opacity-30 disabled:cursor-not-allowed group/delete"
|
||||
className="p-1 sm:p-1.5 text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md transition-all duration-200 disabled:opacity-30 disabled:cursor-not-allowed group/delete"
|
||||
title="Delete item"
|
||||
>
|
||||
<svg className="w-4 h-4 group-hover/delete:scale-110 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg className="w-3 h-3 sm:w-4 sm:h-4 group-hover/delete:scale-110 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
|
|
@ -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<DateConstraintManagerProps> = ({
|
|||
onSave,
|
||||
onCancel
|
||||
}) => {
|
||||
const [notBefore, setNotBefore] = useState<string>(
|
||||
item.not_before ? new Date(item.not_before).toISOString().slice(0, 16) : ''
|
||||
const [notBefore, setNotBefore] = useState<Date | null>(
|
||||
item.not_before ? new Date(item.not_before) : null
|
||||
)
|
||||
const [notAfter, setNotAfter] = useState<string>(
|
||||
item.not_after ? new Date(item.not_after).toISOString().slice(0, 16) : ''
|
||||
const [notAfter, setNotAfter] = useState<Date | null>(
|
||||
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 (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-96 max-w-full mx-4">
|
||||
<h3 className="text-lg font-semibold mb-4">Manage Date Constraints</h3>
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-md mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Manage Date Constraints</h3>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
||||
Set when this item can be completed. Leave empty to remove constraints.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Not Before (Earliest completion time)
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={notBefore}
|
||||
onChange={(e) => setNotBefore(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<div className="relative">
|
||||
<DatePicker
|
||||
selected={notBefore}
|
||||
onChange={(date) => setNotBefore(date)}
|
||||
showTimeSelect
|
||||
timeFormat="HH:mm"
|
||||
timeIntervals={15}
|
||||
dateFormat="MMMM d, yyyy h:mm aa"
|
||||
placeholderText="Select earliest completion time"
|
||||
minDate={now}
|
||||
isClearable
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
|
||||
popperClassName="z-50"
|
||||
popperPlacement="bottom-start"
|
||||
customInput={
|
||||
<input
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{notBefore && (
|
||||
<button
|
||||
onClick={() => setNotBefore(null)}
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Not After (Latest completion time)
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={notAfter}
|
||||
onChange={(e) => setNotAfter(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<div className="relative">
|
||||
<DatePicker
|
||||
selected={notAfter}
|
||||
onChange={(date) => setNotAfter(date)}
|
||||
showTimeSelect
|
||||
timeFormat="HH:mm"
|
||||
timeIntervals={15}
|
||||
dateFormat="MMMM d, yyyy h:mm aa"
|
||||
placeholderText="Select latest completion time"
|
||||
minDate={notBefore || now}
|
||||
isClearable
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
|
||||
popperClassName="z-50"
|
||||
popperPlacement="bottom-start"
|
||||
customInput={
|
||||
<input
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{notAfter && (
|
||||
<button
|
||||
onClick={() => setNotAfter(null)}
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between mt-6">
|
||||
{/* Preview */}
|
||||
{(notBefore || notAfter) && (
|
||||
<div className="mt-6 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Preview:</h4>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
{notBefore && (
|
||||
<div>✅ Can complete after: {notBefore.toLocaleString()}</div>
|
||||
)}
|
||||
{notAfter && (
|
||||
<div>✅ Can complete before: {notAfter.toLocaleString()}</div>
|
||||
)}
|
||||
{notBefore && notAfter && (
|
||||
<div className="text-blue-600 dark:text-blue-400 font-medium">
|
||||
Time window: {Math.round((notAfter.getTime() - notBefore.getTime()) / (1000 * 60 * 60 * 24))} days
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-between gap-3 mt-6">
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="px-4 py-2 text-sm text-gray-600 hover:text-gray-800"
|
||||
className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
<div className="space-x-2">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="px-4 py-2 text-sm text-gray-600 hover:text-gray-800"
|
||||
className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 text-sm bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
className="px-4 py-2 text-sm bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue