/** * Profile Selector Component * * Allows users to manage medication schedule profiles with create, save, * save-as, and delete functionality. Provides a combobox-style interface * for profile selection and management. * * @author Andreas Weyer * @license MIT */ import React, { useState } from 'react'; import { Card, CardContent } from './ui/card'; import { Label } from './ui/label'; import { Input } from './ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from './ui/select'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import { Save, Trash2, Plus, Pencil } from 'lucide-react'; import { IconButtonWithTooltip } from './ui/icon-button-with-tooltip'; import { MAX_PROFILES, type ScheduleProfile } from '../constants/defaults'; interface ProfileSelectorProps { profiles: ScheduleProfile[]; activeProfileId: string; hasUnsavedChanges: boolean; onSwitchProfile: (profileId: string) => void; onSaveProfile: () => void; onSaveProfileAs: (name: string) => string | null; onRenameProfile: (profileId: string, newName: string) => void; onDeleteProfile: (profileId: string) => boolean; t: (key: string) => string; } export const ProfileSelector: React.FC = ({ profiles, activeProfileId, hasUnsavedChanges, onSwitchProfile, onSaveProfile, onSaveProfileAs, onRenameProfile, onDeleteProfile, t, }) => { const [newProfileName, setNewProfileName] = useState(''); const [isSaveAsMode, setIsSaveAsMode] = useState(false); const [isRenameMode, setIsRenameMode] = useState(false); const [renameName, setRenameName] = useState(''); const activeProfile = profiles.find(p => p.id === activeProfileId); const canDelete = profiles.length > 1; const canCreateNew = profiles.length < MAX_PROFILES; // Sort profiles alphabetically (case-insensitive) const sortedProfiles = [...profiles].sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()) ); const handleSelectChange = (value: string) => { if (value === '__new__') { // Enter "save as" mode setIsSaveAsMode(true); setIsRenameMode(false); setNewProfileName(''); } else { // Confirm before switching if there are unsaved changes if (hasUnsavedChanges) { if (!window.confirm(t('profileSwitchUnsavedConfirm'))) { return; } } onSwitchProfile(value); setIsSaveAsMode(false); setIsRenameMode(false); } }; const handleSaveAs = () => { if (!newProfileName.trim()) { return; } // Check for duplicate names const isDuplicate = profiles.some( p => p.name.toLowerCase() === newProfileName.trim().toLowerCase() ); let finalName = newProfileName.trim(); if (isDuplicate) { // Find next available suffix let suffix = 2; while (profiles.some(p => p.name.toLowerCase() === `${newProfileName.trim()} (${suffix})`.toLowerCase())) { suffix++; } finalName = `${newProfileName.trim()} (${suffix})`; } const newProfileId = onSaveProfileAs(finalName); if (newProfileId) { setIsSaveAsMode(false); setNewProfileName(''); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSaveAs(); } else if (e.key === 'Escape') { setIsSaveAsMode(false); setNewProfileName(''); } }; const handleDelete = () => { if (activeProfile && canDelete) { if (window.confirm(t('profileDeleteConfirm')?.replace('{name}', activeProfile.name))) { onDeleteProfile(activeProfile.id); } } }; const handleStartRename = () => { if (activeProfile) { setIsRenameMode(true); setIsSaveAsMode(false); setRenameName(activeProfile.name); } }; const handleRename = () => { if (!renameName.trim() || !activeProfile) { return; } const trimmedName = renameName.trim(); // Check if name is unchanged if (trimmedName === activeProfile.name) { setIsRenameMode(false); return; } // Check for duplicate names (excluding current profile) const isDuplicate = profiles.some( p => p.id !== activeProfile.id && p.name.toLowerCase() === trimmedName.toLowerCase() ); if (isDuplicate) { alert(t('profileNameAlreadyExists') || 'A schedule with this name already exists'); return; } onRenameProfile(activeProfile.id, trimmedName); setIsRenameMode(false); setRenameName(''); }; const handleRenameKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleRename(); } else if (e.key === 'Escape') { setIsRenameMode(false); setRenameName(''); } }; return (
{/* Title label */} {/* Profile selector with integrated buttons */}
{/* Profile selector / name input */} {isSaveAsMode ? ( setNewProfileName(e.target.value)} onKeyDown={handleKeyDown} placeholder={t('profileSaveAsPlaceholder')} autoFocus className="h-9 rounded-r-none border-r-0 w-[288px] bg-background" /> ) : isRenameMode ? ( setRenameName(e.target.value)} onKeyDown={handleRenameKeyDown} placeholder={t('profileRenamePlaceholder')} autoFocus className="h-9 rounded-r-none border-r-0 w-[288px] bg-background" /> ) : ( )} {/* Save button - integrated */} } tooltip={isSaveAsMode ? t('profileSaveAs') : isRenameMode ? t('profileRename') : t('profileSave')} disabled={(isSaveAsMode && !newProfileName.trim()) || (isRenameMode && !renameName.trim()) || (!isSaveAsMode && !isRenameMode && !hasUnsavedChanges)} variant="outline" size="icon" className="rounded-none border-r-0" /> {/* Rename button - integrated */} } tooltip={t('profileRename')} disabled={isSaveAsMode || isRenameMode} variant="outline" size="icon" className="rounded-none border-r-0" /> {/* Delete button - integrated */} } tooltip={canDelete ? t('profileDelete') : t('profileDeleteDisabled')} disabled={!canDelete || isSaveAsMode || isRenameMode} variant="outline" size="icon" className="rounded-l-none text-destructive hover:bg-destructive hover:text-destructive-foreground" />
{/* Helper text for save-as mode */} {isSaveAsMode && (

{t('profileSaveAsHelp')}

)} {/* Helper text for rename mode */} {isRenameMode && (

{t('profileRenameHelp')}

)}
); };