feat(timesheet): add tags management and responsive time tracking flows

This commit is contained in:
2026-04-24 22:23:50 +03:30
parent c4d8379924
commit 987d2e2b59
13 changed files with 3710 additions and 134 deletions

View File

@@ -7,16 +7,18 @@ export interface SelectOption {
label: string;
}
interface SelectProps {
value: string | number;
onChange: (value: string) => void;
options: SelectOption[];
className?: string;
buttonClassName?: string;
isLoading?: boolean;
disabled?: boolean;
loadingText?: string;
}
interface SelectProps {
value: string | number;
onChange: (value: string) => void;
options: SelectOption[];
className?: string;
buttonClassName?: string;
isLoading?: boolean;
disabled?: boolean;
loadingText?: string;
showChevron?: boolean;
portalOwnerId?: string;
}
export const Select: React.FC<SelectProps> = ({
value,
@@ -24,10 +26,12 @@ export const Select: React.FC<SelectProps> = ({
options,
className = "",
buttonClassName = "",
isLoading = false,
disabled = false,
loadingText = "",
}) => {
isLoading = false,
disabled = false,
loadingText = "",
showChevron = true,
portalOwnerId,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
const buttonRef = useRef<HTMLButtonElement>(null);
@@ -106,30 +110,31 @@ export const Select: React.FC<SelectProps> = ({
className={`flex items-center justify-between bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded-md px-3 py-2 text-sm text-slate-700 dark:text-slate-300 outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed ${buttonClassName}`}
>
<span className="truncate">{isLoading ? loadingText : selectedOption?.label}</span>
{isLoading ? (
<svg className="w-4 h-4 ml-2 rtl:ml-0 rtl:mr-2 animate-spin text-slate-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
<svg
className={`w-4 h-4 ml-2 rtl:ml-0 rtl:mr-2 transition-transform ${isOpen ? "rotate-180" : ""}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7"></path>
</svg>
)}
</button>
{isLoading ? (
<svg className="w-4 h-4 ml-2 rtl:ml-0 rtl:mr-2 animate-spin text-slate-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : showChevron ? (
<svg
className={`w-4 h-4 ml-2 rtl:ml-0 rtl:mr-2 transition-transform ${isOpen ? "rotate-180" : ""}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7"></path>
</svg>
) : null}
</button>
{isOpen && !isDisabled &&
createPortal(
<div
ref={dropdownRef}
style={dropdownStyle}
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-md shadow-lg py-1 overflow-y-auto max-h-60"
>
<div
ref={dropdownRef}
style={dropdownStyle}
data-entry-editor-owner={portalOwnerId}
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-md shadow-lg py-1 overflow-y-auto max-h-60"
>
{options.map((option) => (
<div
key={option.value}