tududi/frontend/components/Task/TodaySettingsDropdown.tsx
Antonis Anastasiadis 220bc92b4a
Lint frontend (#131)
* Add lint-fix npm target

* Sync eslint+plugins with backend

* Add prettier

* Ignore no-explicit-any lint rule for now

* Silence eslint react warning

* Format frontend via prettier

* Lint frontend.

---------

Co-authored-by: antanst <>
2025-07-09 12:23:55 +03:00

202 lines
6.9 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import {
ChartBarIcon,
LightBulbIcon,
SparklesIcon,
ClockIcon,
TrophyIcon,
ChatBubbleBottomCenterTextIcon,
} from '@heroicons/react/24/outline';
interface TodaySettingsDropdownProps {
isOpen: boolean;
onClose: () => void;
settings: {
showMetrics: boolean;
showProductivity: boolean;
showIntelligence: boolean;
showDueToday: boolean;
showCompleted: boolean;
showProgressBar: boolean;
showDailyQuote: boolean;
};
onSettingsChange: (settings: any) => void;
}
const TodaySettingsDropdown: React.FC<TodaySettingsDropdownProps> = ({
isOpen,
onClose,
settings,
onSettingsChange,
}) => {
const { t } = useTranslation();
const [localSettings, setLocalSettings] = useState(settings);
const [isSaving, setIsSaving] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setLocalSettings(settings);
}, [settings]);
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
onClose();
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, onClose]);
const handleToggle = (key: keyof typeof localSettings) => {
const newSettings = {
...localSettings,
[key]: !localSettings[key],
};
setLocalSettings(newSettings);
// Auto-save on change
saveSettings(newSettings);
};
const saveSettings = async (settingsToSave: typeof localSettings) => {
setIsSaving(true);
try {
const response = await fetch('/api/profile/today-settings', {
method: 'PUT',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(settingsToSave),
});
if (response.ok) {
onSettingsChange(settingsToSave);
} else {
console.error('Failed to save settings');
}
} catch (error) {
console.error('Error saving settings:', error);
} finally {
setIsSaving(false);
}
};
if (!isOpen) return null;
const settingsOptions: Array<{
key: keyof typeof localSettings;
label: string;
icon: React.ElementType;
disabled?: boolean;
}> = [
{
key: 'showDailyQuote',
label: t('settings.showDailyQuote', 'Show Daily Quote'),
icon: ChatBubbleBottomCenterTextIcon,
},
{
key: 'showMetrics',
label: t('settings.showMetrics', 'Show Metrics'),
icon: ChartBarIcon,
},
{
key: 'showProductivity',
label: t('settings.showProductivity', 'Show Productivity Insights'),
icon: LightBulbIcon,
},
{
key: 'showIntelligence',
label: t(
'settings.showIntelligence',
'Show Intelligence Suggestions'
),
icon: SparklesIcon,
},
{
key: 'showDueToday',
label: t('settings.showDueToday', 'Show Due Today Tasks'),
icon: ClockIcon,
},
{
key: 'showCompleted',
label: t('settings.showCompleted', 'Show Completed Tasks'),
icon: TrophyIcon,
},
];
return (
<div
ref={dropdownRef}
className="absolute right-0 top-full mt-2 w-72 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-50"
>
<div className="p-4">
<h3 className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3">
{t('settings.todayPageSettings', 'Today Page Settings')}
</h3>
<div className="space-y-3">
{settingsOptions.map((option) => {
const IconComponent = option.icon;
const isDisabled = option.disabled || isSaving;
return (
<div
key={option.key}
className={`flex items-center justify-between ${isDisabled ? 'opacity-60' : ''}`}
>
<div className="flex items-center flex-1">
<IconComponent className="h-4 w-4 text-gray-500 dark:text-gray-400 mr-3" />
<label
className={`text-sm text-gray-700 dark:text-gray-300 ${!isDisabled ? 'cursor-pointer' : 'cursor-not-allowed'} flex-1`}
>
{option.label}
</label>
</div>
<button
onClick={() =>
!isDisabled && handleToggle(option.key)
}
disabled={isDisabled}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
localSettings[option.key]
? 'bg-blue-600'
: 'bg-gray-200 dark:bg-gray-700'
}`}
>
<span
className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${
localSettings[option.key]
? 'translate-x-5'
: 'translate-x-1'
}`}
/>
</button>
</div>
);
})}
</div>
{isSaving && (
<div className="mt-3 text-xs text-gray-500 dark:text-gray-400">
{t('common.saving', 'Saving...')}
</div>
)}
</div>
</div>
);
};
export default TodaySettingsDropdown;