This commit is contained in:
lubiana 2025-07-25 17:02:17 +02:00
parent 4ed9538a93
commit c7eae58857
Signed by: lubiana
SSH key fingerprint: SHA256:vW1EA0fRR3Fw+dD/sM0K+x3Il2gSry6YRYHqOeQwrfk
5 changed files with 446 additions and 61 deletions

View file

@ -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",

View file

@ -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"

View file

@ -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>
{/* Constraint warnings - compact on mobile */}
{!item.checked && !canComplete && (
<div className="mt-1 flex flex-wrap gap-1">
{/* 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(', ')}
{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 */}
{!item.checked && !dateConstraintsMet && (
<div className="mt-1 text-xs text-red-600 dark:text-red-400">
Date constraint: {
notBeforeDate && now < notBeforeDate
{!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>

View file

@ -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>
<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
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"
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>
<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
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"
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>

View file

@ -4,3 +4,162 @@
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;
}