/** * My Availability Page * * Staff-facing page to view and manage their own time blocks. * Uses the same UI as TimeBlocks but locked to the staff's own resource. * Shows business-level blocks (read-only) and personal blocks (editable). */ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useOutletContext } from 'react-router-dom'; import { TimeBlockListItem, BlockType, RecurrenceType, User, } from '../types'; import { useMyBlocks, useCreateTimeBlock, useUpdateTimeBlock, useDeleteTimeBlock, useToggleTimeBlock, useHolidays, } from '../hooks/useTimeBlocks'; import Portal from '../components/Portal'; import YearlyBlockCalendar from '../components/time-blocks/YearlyBlockCalendar'; import TimeBlockCreatorModal from '../components/time-blocks/TimeBlockCreatorModal'; import { Calendar, Building2, User as UserIcon, Plus, Pencil, Trash2, AlertTriangle, Clock, CalendarDays, Ban, AlertCircle, Power, PowerOff, Info, CheckCircle, XCircle, HourglassIcon, } from 'lucide-react'; type AvailabilityTab = 'blocks' | 'calendar'; const RECURRENCE_TYPE_LABELS: Record = { NONE: 'One-time', WEEKLY: 'Weekly', MONTHLY: 'Monthly', YEARLY: 'Yearly', HOLIDAY: 'Holiday', }; const BLOCK_TYPE_LABELS: Record = { HARD: 'Hard Block', SOFT: 'Soft Block', }; interface MyAvailabilityProps { user?: User; } const MyAvailability: React.FC = (props) => { const { t } = useTranslation(); const contextUser = useOutletContext<{ user?: User }>()?.user; const user = props.user || contextUser; const [activeTab, setActiveTab] = useState('blocks'); const [isModalOpen, setIsModalOpen] = useState(false); const [editingBlock, setEditingBlock] = useState(null); const [deleteConfirmId, setDeleteConfirmId] = useState(null); // Fetch data const { data: myBlocksData, isLoading } = useMyBlocks(); const { data: holidays = [] } = useHolidays('US'); // Mutations const createBlock = useCreateTimeBlock(); const updateBlock = useUpdateTimeBlock(); const deleteBlock = useDeleteTimeBlock(); const toggleBlock = useToggleTimeBlock(); // Modal handlers const openCreateModal = () => { setEditingBlock(null); setIsModalOpen(true); }; const openEditModal = (block: TimeBlockListItem) => { setEditingBlock(block); setIsModalOpen(true); }; const closeModal = () => { setIsModalOpen(false); setEditingBlock(null); }; const handleDelete = async (id: string) => { try { await deleteBlock.mutateAsync(id); setDeleteConfirmId(null); } catch (error) { console.error('Failed to delete time block:', error); } }; const handleToggle = async (id: string) => { try { await toggleBlock.mutateAsync(id); } catch (error) { console.error('Failed to toggle time block:', error); } }; // Render block type badge const renderBlockTypeBadge = (type: BlockType) => ( {type === 'HARD' ? : } {BLOCK_TYPE_LABELS[type]} ); // Render recurrence badge const renderRecurrenceBadge = (type: RecurrenceType) => ( {type === 'HOLIDAY' ? ( ) : type === 'NONE' ? ( ) : ( )} {RECURRENCE_TYPE_LABELS[type]} ); // Render approval status badge const renderApprovalBadge = (status: string | undefined) => { if (!status || status === 'APPROVED') { return ( {t('myAvailability.approved', 'Approved')} ); } if (status === 'PENDING') { return ( {t('myAvailability.pending', 'Pending Review')} ); } if (status === 'DENIED') { return ( {t('myAvailability.denied', 'Denied')} ); } return null; }; // Handle no linked resource if (!isLoading && !myBlocksData?.resource_id) { return (

{t('myAvailability.title', 'My Availability')}

{t('myAvailability.subtitle', 'Manage your time off and unavailability')}

{t('myAvailability.noResource', 'No Resource Linked')}

{t('myAvailability.noResourceDesc', 'Your account is not linked to a resource. Please contact your manager to set up your availability.')}

); } // Create a mock resource for the modal const staffResource = myBlocksData?.resource_id ? { id: myBlocksData.resource_id, name: myBlocksData.resource_name || 'My Resource', } : null; return (
{/* Header */}

{t('myAvailability.title', 'My Availability')}

{t('myAvailability.subtitle', 'Manage your time off and unavailability')}

{/* Approval Required Banner */} {myBlocksData?.can_self_approve === false && (

{t('myAvailability.approvalRequired', 'Approval Required')}

{t('myAvailability.approvalRequiredInfo', 'Your time off requests require manager or owner approval. New blocks will show as "Pending Review" until approved.')}

)} {/* Business Blocks Info Banner */} {myBlocksData?.business_blocks && myBlocksData.business_blocks.length > 0 && (

{t('myAvailability.businessBlocks', 'Business Closures')}

{t('myAvailability.businessBlocksInfo', 'These blocks are set by your business and apply to everyone:')}

{myBlocksData.business_blocks.map((block) => ( {block.title} {renderRecurrenceBadge(block.recurrence_type)} ))}
)} {/* Tabs */}
{/* Tab Content */} {isLoading ? (
) : (
{activeTab === 'blocks' && ( <> {/* Resource Info Banner */}

{t('myAvailability.resourceInfo', 'Managing blocks for:')} {myBlocksData?.resource_name}

{/* My Blocks List */} {myBlocksData?.my_blocks && myBlocksData.my_blocks.length === 0 ? (

{t('myAvailability.noBlocks', 'No Time Blocks')}

{t('myAvailability.noBlocksDesc', 'Add time blocks for vacations, lunch breaks, or any time you need off.')}

) : (
{myBlocksData?.my_blocks.map((block) => ( ))}
{t('myAvailability.titleCol', 'Title')} {t('myAvailability.typeCol', 'Type')} {t('myAvailability.patternCol', 'Pattern')} {t('myAvailability.statusCol', 'Status')} {t('myAvailability.actionsCol', 'Actions')}
{block.title} {!block.is_active && ( Inactive )}
{renderBlockTypeBadge(block.block_type)}
{renderRecurrenceBadge(block.recurrence_type)} {block.pattern_display && ( {block.pattern_display} )}
{renderApprovalBadge((block as any).approval_status)} {(block as any).review_notes && ( "{(block as any).review_notes}" )}
)} )} {activeTab === 'calendar' && (
{ // Find the block and open edit modal if it's my block const block = myBlocksData?.my_blocks.find(b => b.id === blockId); if (block) { openEditModal(block); } }} />
)}
)} {/* Create/Edit Modal - Using TimeBlockCreatorModal in staff mode */} { try { if (editingBlock) { await updateBlock.mutateAsync({ id: editingBlock.id, updates: data }); } else { // Handle array of blocks (multiple holidays) const blocks = Array.isArray(data) ? data : [data]; for (const block of blocks) { await createBlock.mutateAsync(block); } } closeModal(); } catch (error) { console.error('Failed to save time block:', error); } }} isSubmitting={createBlock.isPending || updateBlock.isPending} editingBlock={editingBlock} holidays={holidays} resources={staffResource ? [staffResource as any] : []} isResourceLevel={true} staffMode={true} staffResourceId={myBlocksData?.resource_id} /> {/* Delete Confirmation Modal */} {deleteConfirmId && (

{t('myAvailability.deleteConfirmTitle', 'Delete Time Block?')}

{t('myAvailability.deleteConfirmDesc', 'This action cannot be undone.')}

)}
); }; export default MyAvailability;