* Add next suggestions and remove console logs * Add pomodoro timer * Add pomodoro switch in settings * Fix pomodoro setting * Add timezones to settings * Fix an issue with password reset * Cleanup * Sort tags alphabetically * Clean up today's view * Add an indicator for repeatedly added to today * Refactor tags * Add due date today item * Move recurrence to the subtitle area * Fix today layout * Add a badge to Inbox items * Move inbox badge to sidebar * Add quotes and progress bar * Add translations for quotes * Fix test issues * Add helper script for docker local * Set up overdue tasks * Add linux/arm/v7 build to deploy script * Add linux/arm/v7 build to deploy script pt2 * Fix an issue with helmet and SSL * Add volume db persistence * Fix cog icon issues
158 lines
No EOL
5.3 KiB
TypeScript
158 lines
No EOL
5.3 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; |