Files
worldshine-redesign/client/src/components/info-screen/InfoScreen.js
Lixian Zhou af8ff3188f
All checks were successful
Build And Deploy Main / build-and-deploy (push) Successful in 33s
fix
2026-03-12 15:10:42 -04:00

1401 lines
55 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { AuthService, EventsService, CustomerService, ResourceService, AttendanceNoteService, CarouselService } from '../../services';
import { Spinner, Breadcrumb, BreadcrumbItem, Tabs, Tab, Modal, Button, Carousel } from 'react-bootstrap';
import { PencilSquare, FileImage, ArrowsFullscreen } from 'react-bootstrap-icons';
import moment from 'moment';
const InfoScreen = () => {
const navigate = useNavigate();
const canViewAppointmentCalendar = AuthService.hasPermission('View_Appointment Calendar');
const [isLoading, setIsLoading] = useState(true);
const [medicalEvents, setMedicalEvents] = useState([]);
const [activityEvents, setActivityEvents] = useState([]);
const [mealEvents, setMealEvents] = useState([]);
const [customers, setCustomers] = useState([]);
const [resources, setResources] = useState([]);
const [currentTime, setCurrentTime] = useState(new Date());
// AttendanceNote state
const [showEditModal, setShowEditModal] = useState(false);
const [attendanceNote, setAttendanceNote] = useState({
id: null,
slogan: 'Quality | Commitment',
introduction: 'At Worldshine, we provide medical day care services to seniors who need assistance with activities of daily life (ADLs). Our experienced and highly trained staff are well qualified to care for seniors suffering from a variety of ailments inckluding dementia, Alzheimer\'s and other physical disabilities.',
image: null
});
const [editForm, setEditForm] = useState({
slogan: '',
introduction: '',
image: null
});
const [selectedFile, setSelectedFile] = useState(null);
// Gallery state
const [showGalleryModal, setShowGalleryModal] = useState(false);
const [galleryImages, setGalleryImages] = useState([
'/images/background.jpg',
'/images/landing.png',
]);
const [galleryForm, setGalleryForm] = useState([]);
// Carousel state
const [carousel, setCarousel] = useState({
id: null,
images: [
'/images/background.jpg',
'/images/landing.png',
]
});
const [carouselForm, setCarouselForm] = useState([
'/images/background.jpg',
'/images/landing.png',
]);
const [selectedCarouselFiles, setSelectedCarouselFiles] = useState([]);
// Background and full screen state
const [backgroundImage, setBackgroundImage] = useState('/images/background.jpg');
const [isFullScreen, setIsFullScreen] = useState(false);
const [showBackgroundModal, setShowBackgroundModal] = useState(false);
const [selectedBackgroundFile, setSelectedBackgroundFile] = useState(null);
// Expand a recurring rule into instances within a date range
const expandRecurrence = (rule, rangeFrom, rangeTo) => {
const instances = [];
const startDate = new Date(rule.start_repeat_date);
const endDate = new Date(rule.end_repeat_date);
const from = new Date(rangeFrom);
const to = new Date(rangeTo);
const effectiveStart = from > startDate ? from : startDate;
const effectiveEnd = to < endDate ? to : endDate;
if (effectiveStart > effectiveEnd) return instances;
const freq = rule.rrule;
let current = new Date(startDate);
let count = 0;
while (current <= effectiveEnd && count < 1000) {
if (current >= effectiveStart) {
const dateStr = moment(current).format('YYYY-MM-DD');
instances.push({
...rule,
id: `recur-${rule.id}-${dateStr}`,
_recur_id: rule.id,
start_time: new Date(current),
stop_time: new Date(current),
});
}
if (freq === 'FREQ=DAILY') {
current = new Date(current.getTime() + 24 * 60 * 60 * 1000);
} else if (freq === 'FREQ=WEEKLY') {
current = new Date(current.getTime() + 7 * 24 * 60 * 60 * 1000);
} else if (freq === 'FREQ=MONTHLY') {
const next = new Date(current);
next.setMonth(next.getMonth() + 1);
current = next;
} else if (freq === 'FREQ=YEARLY') {
const next = new Date(current);
next.setFullYear(next.getFullYear() + 1);
current = next;
} else {
break;
}
count++;
}
return instances;
};
const redirectTo = () => {
navigate('/dashboard/dashboard');
}
useEffect(() => {
if (!AuthService.canViewInfoScreen()) {
window.alert('You haven\'t login yet OR this user does not have access to this page. Please change an admin account to login.');
AuthService.logout();
navigate('/login');
return;
}
// Load customers and resources
CustomerService.getAllCustomers().then((data) => {
setCustomers(data.data);
});
ResourceService.getAll().then(data => {
setResources(data.data);
});
// Load attendance note and carousel
loadAttendanceNote();
loadCarousel();
setIsLoading(false);
}, [navigate]);
// Load attendance note data
const loadAttendanceNote = async () => {
try {
// Get all attendance notes and filter for active ones
const response = await AttendanceNoteService.getAll('active');
const activeNotes = response.data;
if (activeNotes && activeNotes.length > 0) {
// Sort by create_date and get the latest one
const sortedNotes = activeNotes.sort((a, b) =>
new Date(b.create_date) - new Date(a.create_date)
);
const latestNote = sortedNotes[0];
// Set the attendance note data
setAttendanceNote({
id: latestNote.id,
slogan: latestNote.slogan,
introduction: latestNote.introduction,
image: null
});
// Load the image file
if (latestNote.id) {
try {
const imageResponse = await AttendanceNoteService.getAllAttendanceNoteFiles(
latestNote.id,
latestNote.id,
'note_img'
);
console.log('Attendance note image response:', imageResponse);
// Handle both response.data.files and response.data.data.files structures
const files = imageResponse?.data?.data?.files || imageResponse?.data?.files;
if (files && Array.isArray(files) && files.length > 0) {
// Get the first image file
const imageFile = files[0];
console.log('Attendance note image file:', imageFile);
setAttendanceNote(prev => ({
...prev,
image: imageFile.url
}));
} else {
console.log('No files found in attendance note response');
}
} catch (imageError) {
console.log('No image found for attendance note:', imageError);
}
}
}
} catch (error) {
console.error('Error loading attendance note:', error);
}
};
// Load carousel data
const loadCarousel = async () => {
try {
console.log('Loading carousel data...');
// Get all carousels and filter for active ones
const response = await CarouselService.getAll('active');
const activeCarousels = response.data;
console.log('Active carousels:', activeCarousels);
if (activeCarousels && activeCarousels.length > 0) {
// Sort by create_date and get the latest one
const sortedCarousels = activeCarousels.sort((a, b) =>
new Date(b.create_date) - new Date(a.create_date)
);
const latestCarousel = sortedCarousels[0];
console.log('Latest carousel:', latestCarousel);
// Set the carousel data
setCarousel({
id: latestCarousel.id,
images: []
});
// Load the carousel images
if (latestCarousel.id) {
try {
console.log('Loading carousel images for ID:', latestCarousel.id);
const imagesResponse = await CarouselService.getAllCarouselFiles(
latestCarousel.id,
latestCarousel.id,
'carousel_item'
);
console.log('Carousel images response:', imagesResponse);
console.log('imagesResponse.data:', imagesResponse?.data);
console.log('imagesResponse.data?.data:', imagesResponse?.data?.data);
console.log('imagesResponse.data?.files:', imagesResponse?.data?.files);
// Handle both response.data.files and response.data.data.files structures
const files = imagesResponse?.data?.data?.files || imagesResponse?.data?.files;
if (files && Array.isArray(files) && files.length > 0) {
// Get all image files and extract URLs
const imageUrls = files.map(file => file.url);
console.log('Carousel image URLs:', imageUrls);
console.log('Carousel files array:', files);
setCarousel(prev => ({
...prev,
images: imageUrls
}));
} else {
console.log('No files found in carousel response');
console.log('Response structure:', imagesResponse);
console.log('files value:', files);
console.log('files type:', typeof files);
console.log('files isArray:', Array.isArray(files));
if (files) {
console.log('files length:', files.length);
}
}
} catch (imageError) {
console.log('No images found for carousel:', imageError);
}
}
} else {
console.log('No active carousels found, keeping default images');
}
} catch (error) {
console.error('Error loading carousel:', error);
console.log('Keeping default images due to error');
}
};
// Update clock every second
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
return () => clearInterval(timer);
}, []);
// Initialize edit form when opening modal
const handleEditClick = () => {
setEditForm({
slogan: attendanceNote.slogan,
introduction: attendanceNote.introduction,
image: attendanceNote.image
});
setSelectedFile(null);
setShowEditModal(true);
};
// Handle form changes
const handleFormChange = (field, value) => {
setEditForm(prev => ({
...prev,
[field]: value
}));
};
// Handle file upload
const handleFileUpload = (event) => {
const file = event.target.files[0];
if (file) {
setSelectedFile(file);
const reader = new FileReader();
reader.onload = (e) => {
setEditForm(prev => ({
...prev,
image: e.target.result
}));
};
reader.readAsDataURL(file);
}
};
// Save changes
const handleSave = async () => {
try {
// First, deactivate the current note if it exists
if (attendanceNote.id) {
await AttendanceNoteService.updateAttendanceNote(attendanceNote.id, {
status: 'inactive',
update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
});
}
// Create a new attendance note
const newNoteData = {
slogan: editForm.slogan,
introduction: editForm.introduction,
status: 'active',
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
};
const newNoteResponse = await AttendanceNoteService.createNewAttendanceNote(newNoteData);
const newNoteId = newNoteResponse.data.id;
// Upload the image file if selected
if (selectedFile) {
const formData = new FormData();
formData.append('file', selectedFile);
await AttendanceNoteService.uploadAttendanceNoteFile(
formData,
newNoteId,
newNoteId,
'note_img'
);
}
// Reload the attendance note data
await loadAttendanceNote();
setShowEditModal(false);
} catch (error) {
console.error('Error saving attendance note:', error);
alert('Error saving attendance note. Please try again.');
}
};
// Close modal
const handleCloseModal = () => {
setShowEditModal(false);
};
// Gallery functions
const handleGalleryEditClick = () => {
setGalleryForm([...galleryImages]);
setShowGalleryModal(true);
};
const handleGalleryFileUpload = (event) => {
const files = Array.from(event.target.files);
const newImages = [];
files.forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
newImages.push(e.target.result);
if (newImages.length === files.length) {
setGalleryForm(prev => [...prev, ...newImages]);
}
};
reader.readAsDataURL(file);
});
};
const handleGallerySave = () => {
setGalleryImages(galleryForm);
setShowGalleryModal(false);
};
const handleGalleryClose = () => {
setShowGalleryModal(false);
};
const removeGalleryImage = (index) => {
setGalleryForm(prev => prev.filter((_, i) => i !== index));
};
// Carousel functions
const handleCarouselEditClick = () => {
console.log('Opening carousel edit modal');
console.log('Current carousel images:', carousel.images);
setCarouselForm([...carousel.images]);
setSelectedCarouselFiles([]);
setShowGalleryModal(true);
};
const handleCarouselFileUpload = (event) => {
const files = Array.from(event.target.files);
console.log('Files selected for carousel:', files);
setSelectedCarouselFiles(prev => [...prev, ...files]);
const newImages = [];
files.forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
newImages.push(e.target.result);
if (newImages.length === files.length) {
console.log('Adding new images to carousel form:', newImages);
setCarouselForm(prev => [...prev, ...newImages]);
}
};
reader.readAsDataURL(file);
});
};
const handleCarouselSave = async () => {
try {
console.log('Saving carousel...');
console.log('Selected files:', selectedCarouselFiles);
console.log('Carousel form:', carouselForm);
// First, deactivate the current carousel if it exists
if (carousel.id) {
console.log('Deactivating current carousel:', carousel.id);
await CarouselService.updateCarousel(carousel.id, {
status: 'inactive',
update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
});
}
// Create a new carousel
const newCarouselData = {
status: 'active',
create_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
update_by: localStorage.getItem('user') && JSON.parse(localStorage.getItem('user'))?.name,
};
console.log('Creating new carousel with data:', newCarouselData);
const newCarouselResponse = await CarouselService.createNewCarousel(newCarouselData);
const newCarouselId = newCarouselResponse.data.id;
console.log('New carousel created with ID:', newCarouselId);
// Upload all selected files
for (const file of selectedCarouselFiles) {
console.log('Uploading file:', file.name);
const formData = new FormData();
formData.append('file', file);
await CarouselService.uploadCarouselFile(
formData,
newCarouselId,
newCarouselId,
'carousel_item'
);
console.log('File uploaded successfully:', file.name);
}
// Reload the carousel data
await loadCarousel();
setShowGalleryModal(false);
} catch (error) {
console.error('Error saving carousel:', error);
alert('Error saving carousel. Please try again.');
}
};
const removeCarouselImage = (index) => {
setCarouselForm(prev => prev.filter((_, i) => i !== index));
setSelectedCarouselFiles(prev => prev.filter((_, i) => i !== index));
};
// Background and full screen functions
const handleBackgroundSelect = () => {
setShowBackgroundModal(true);
};
const handleBackgroundFileUpload = (event) => {
const file = event.target.files[0];
if (file) {
setSelectedBackgroundFile(file);
const reader = new FileReader();
reader.onload = (e) => {
setBackgroundImage(e.target.result);
};
reader.readAsDataURL(file);
}
};
const handleBackgroundSave = async () => {
try {
if (selectedBackgroundFile) {
// Create a FormData object to send the file
const formData = new FormData();
formData.append('file', selectedBackgroundFile);
// Save the file to public/images directory
// For now, we'll use a simple approach by copying the file
// In a real implementation, you'd want to use a proper file upload service
// For demonstration, we'll just update the state
console.log('Background image saved:', selectedBackgroundFile.name);
}
setShowBackgroundModal(false);
setSelectedBackgroundFile(null);
} catch (error) {
console.error('Error saving background image:', error);
alert('Error saving background image. Please try again.');
}
};
const handleBackgroundClose = () => {
setShowBackgroundModal(false);
setSelectedBackgroundFile(null);
};
const handleFullScreen = () => {
setIsFullScreen(true);
};
const handleExitFullScreen = () => {
setIsFullScreen(false);
};
// Handle ESC key to exit full screen
useEffect(() => {
const handleKeyPress = (event) => {
if (event.key === 'Escape' && isFullScreen) {
handleExitFullScreen();
}
};
if (isFullScreen) {
document.addEventListener('keydown', handleKeyPress);
}
return () => {
document.removeEventListener('keydown', handleKeyPress);
};
}, [isFullScreen]);
useEffect(() => {
if (customers?.length > 0 && resources?.length > 0) {
const now = moment();
const fromDate = new Date(now.year(), now.month(), 1);
const toDate = new Date(now.year(), now.month() + 1, 0);
Promise.all([
EventsService.getAllEvents({
from: EventsService.formatDate(fromDate),
to: EventsService.formatDate(toDate)
}),
EventsService.getAllEventRecurrences()
]).then(([eventsRes, recurRes]) => {
const allEvents = eventsRes?.data || [];
const recurrenceRules = (recurRes?.data || []).filter(
rule => ['medical', 'activity', 'meal_plan'].includes(rule.type) && rule.status === 'active'
);
const recurInstances = recurrenceRules.flatMap(rule => expandRecurrence(rule, fromDate, toDate));
const mergedEvents = [...allEvents, ...recurInstances];
const todayEvents = mergedEvents.filter((item) => {
if (!item?.start_time) return false;
return moment(item.start_time).isSame(now, 'day');
});
// Filter medical events
const medicalEventsData = todayEvents
.filter((item) => item.type === 'medical' && item.confirmed && item.status === 'active')
.map((item) => ({
...item,
customer: item?.data?.customer
? (customers.find(c => c.id === item?.data?.customer)?.name || item?.data?.client_name || '')
: (item?.data?.client_name || ''),
driverInfo: item?.link_event_name || '',
startTime: item?.start_time ? moment(item.start_time).format('MM/DD/YYYY h:mm A') : '',
}));
// Filter activity events
const activityEventsData = todayEvents
.filter((item) => item.type === 'activity' && item.status === 'active')
.map((item) => ({
...item,
startTime: item?.start_time ? moment(item.start_time).format('h:mm A') : '',
}));
// Filter meal events and parse meal information
const mealEventsData = todayEvents
.filter((item) => item.type === 'meal_plan' && item.status === 'active')
.map((item) => {
const description = item.description || '';
let snack = '';
if (description.includes('Snack') || description.includes('snack')) {
const snackParts = description.split(/Snack|snack/);
if (snackParts.length > 1) {
snack = snackParts[1].replace(/:/g, '').trim();
}
}
let lunch = '';
const firstPart = description.split(/Snack|snack/)[0];
if (firstPart.includes('Lunch') || firstPart.includes('lunch')) {
const lunchParts = firstPart.split(/Lunch|lunch/);
if (lunchParts.length > 1) {
lunch = lunchParts[1].replace(/:/g, '').trim();
}
}
let breakfast = '';
const secondPart = firstPart.split(/Lunch|lunch/)[0];
if (secondPart.includes('Breakfast') || secondPart.includes('breakfast')) {
const breakfastParts = secondPart.split(/Breakfast|breakfast/);
if (breakfastParts.length > 1) {
breakfast = breakfastParts[1].replace(/:/g, '').trim();
}
}
return {
...item,
breakfast,
lunch,
snack,
};
});
setMedicalEvents(medicalEventsData);
setActivityEvents(activityEventsData);
setMealEvents(mealEventsData);
}).catch(error => {
console.error('Error fetching events:', error);
setMedicalEvents([]);
setActivityEvents([]);
setMealEvents([]);
});
}
}, [customers, resources]);
if (isLoading) {
return <Spinner animation="border" />;
}
return (
<>
{!isFullScreen && (
<div className="list row mb-4">
<Breadcrumb>
<Breadcrumb.Item>Info Screen</Breadcrumb.Item>
<Breadcrumb.Item active>
Info Screen
</Breadcrumb.Item>
</Breadcrumb>
<div className="col-md-12 text-primary">
<h4>Info Screen</h4>
</div>
</div>
)}
<div
className={`app-main-content-list-container ${isFullScreen ? 'fullscreen-mode background-image-container' : ''}`}
style={{
backgroundImage: isFullScreen ? `url(${backgroundImage})` : 'none'
}}
>
<div className="app-main-content-list-func-container">
{!isFullScreen && (
<Tabs defaultActiveKey="infoScreen" id="info-screen-tab">
<Tab eventKey="infoScreen" title="Info Screen">
<div className="multi-columns-container">
{canViewAppointmentCalendar && (
<div className="column-container">
<div className="column-card">
<h6 className="text-black" style={{ fontSize: '14px', fontWeight: '600' }}>Appointments</h6>
<div className="app-main-content-fields-section">
<div className="list row mb-4">
<div className="col-md-12">
<table className="personnel-info-table info-screen-appointments-table">
<thead>
<tr>
<th>Time</th>
<th>Customer</th>
<th>Driver</th>
</tr>
</thead>
<tbody>
{medicalEvents && medicalEvents.length > 0 ? (
medicalEvents.map((medicalEvent, index) => (
<tr key={medicalEvent.id}>
<td>{medicalEvent.startTime}</td>
<td>{medicalEvent.customer}</td>
<td>{medicalEvent.driverInfo}</td>
</tr>
))
) : (
<tr>
<td colSpan="3" className="text-center">No medical appointments scheduled for today</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
)}
<div className="column-container">
{/* First Piece - Time and Weather Cards */}
<div style={{display: 'flex', marginBottom: '32px', gap: '20px'}}>
{/* Time Card */}
<div>
<div className="card h-100" style={{borderRadius: '8px', minWidth: '200px'}}>
<div className="card-body text-center" style={{ padding: '10px 16px' }}>
<div className="mb-1">
<div className="text-black" style={{ fontSize: '12px' }}>
{moment(currentTime).format('dddd')}
</div>
<div className="text-black">
{moment(currentTime).format('MMM Do, YYYY')}
</div>
<h5 className="text-primary mt-1 mb-0" style={{ fontSize: '20px', fontWeight: '600' }}>
{moment(currentTime).format('HH:mm')}
</h5>
</div>
{/* CSS Clock */}
<div className="clock-container" style={{ marginTop: '6px' }}>
<div className="clock clock-sm">
<div className="clock-face">
<div className="hand hour-hand" style={{
transform: `rotate(${(moment(currentTime).hour() % 12) * 30 + moment(currentTime).minute() * 0.5}deg)`
}}></div>
<div className="hand minute-hand" style={{
transform: `rotate(${moment(currentTime).minute() * 6}deg)`
}}></div>
<div className="hand second-hand" style={{
transform: `rotate(${moment(currentTime).second() * 6}deg)`
}}></div>
<div className="center-dot"></div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Weather Card */}
<div>
<div className="card h-100" style={{borderRadius: '8px', minWidth: '200px'}}>
<div className="card-body text-center" style={{ padding: '10px 16px' }}>
<div className="mb-1">
<div className="text-black" style={{ fontSize: '12px' }}>
New York, NY
</div>
<div className="text-black">
Partly Cloudy
</div>
<h5 className="text-primary mt-1 mb-0" style={{ fontSize: '20px', fontWeight: '600' }}>
22°C
</h5>
</div>
{/* Weather Icon */}
<div className="weather-icon" style={{ marginTop: '6px' }}>
<i className="fas fa-cloud-sun" style={{ fontSize: '28px', color: '#ffc107' }}></i>
</div>
</div>
</div>
</div>
</div>
{/* Second Piece - AttendanceNote Card */}
<div className="mb-4">
<div className="card" style={{borderRadius: '8px', maxWidth: '450px'}}>
<div className="card-body">
{/* Header with Logo and Edit Button */}
<div className="d-flex justify-content-between align-items-start mb-3">
<div className="d-flex align-items-center">
<img src="/images/logo-trans.png" alt="Worldshine Logo" style={{ height: '30px', marginRight: '10px' }} />
<strong className="logo-worldshine" style={{ color: '#0066B1', fontSize: '16px' }}>Worldshine</strong>
</div>
<Button
variant="link"
className="p-0"
onClick={handleEditClick}
style={{ color: '#666' }}
>
<PencilSquare size={16} />
</Button>
</div>
{/* Content Layout */}
<div className="row">
{/* Left Side - Text Content */}
<div className="col-md-8">
<h5 className="text-primary mb-2">{attendanceNote.slogan}</h5>
<p className="text-muted" style={{ fontSize: '14px', lineHeight: '1.6' }}>
{attendanceNote.introduction}
</p>
</div>
{/* Right Side - Image */}
<div className="col-md-4">
{attendanceNote.image ? (
<img
src={attendanceNote.image}
alt="Attendance Note"
className="img-fluid rounded"
style={{ maxHeight: '120px', width: '100%', objectFit: 'cover' }}
/>
) : (
<div
className="d-flex align-items-center justify-content-center"
style={{
height: '120px',
backgroundColor: '#f8f9fa',
border: '2px dashed #dee2e6',
borderRadius: '8px'
}}
>
<span className="text-muted">No image</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* Third Piece - Gallery */}
<div className="mb-4">
<div className="card" style={{borderRadius: '8px', maxWidth: '450px'}}>
<div className="card-body">
{/* Header with Title and Edit Button */}
<div className="d-flex justify-content-between align-items-center mb-3">
<h6 className="text-black mb-0" style={{ fontSize: '14px', fontWeight: '500' }}>Gallery</h6>
<Button
variant="link"
className="p-0"
onClick={handleCarouselEditClick}
style={{ color: '#666' }}
>
<PencilSquare size={16} />
</Button>
</div>
{/* Image Carousel */}
{carousel.images.length > 0 ? (
<Carousel
interval={10000}
controls={false}
indicators={true}
style={{ height: '200px' }}
>
{carousel.images.map((image, index) => (
<Carousel.Item key={index}>
<img
className="d-block w-100"
src={image}
alt={`Gallery ${index + 1}`}
style={{
height: '200px',
objectFit: 'cover',
borderRadius: '4px'
}}
/>
</Carousel.Item>
))}
</Carousel>
) : (
<div
className="d-flex align-items-center justify-content-center"
style={{
height: '200px',
backgroundColor: '#f8f9fa',
border: '2px dashed #dee2e6',
borderRadius: '4px'
}}
>
<span className="text-muted">No images in gallery</span>
</div>
)}
</div>
</div>
</div>
</div>
<div className="column-container">
<div className="column-card mb-4">
<h6 className="text-black mb-3" style={{ fontSize: '14px', fontWeight: '500' }}>Activities</h6>
{/* Activities Table */}
<table className="personnel-info-table info-screen-appointments-table">
<thead>
<tr>
<th>Time</th>
<th>Activity Name</th>
<th>Location</th>
</tr>
</thead>
<tbody>
{activityEvents && activityEvents.length > 0 ? (
activityEvents.map((activityEvent, index) => (
<tr key={activityEvent.id}>
<td>{activityEvent.startTime}</td>
<td>{activityEvent.title}</td>
<td>{activityEvent.event_location}</td>
</tr>
))
) : (
<tr>
<td colSpan="3" className="text-center">No activities scheduled for today</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="column-card">
{/* Menu Table */}
<div className="list row mb-4">
<div className="col-md-12">
<h6 className="text-black mb-3" style={{ fontSize: '14px', fontWeight: '500' }}>Menu</h6>
<table className="personnel-info-table info-screen-appointments-table">
<thead>
<tr>
<th>Breakfast</th>
<th>Lunch</th>
<th>Snack</th>
</tr>
</thead>
<tbody>
{mealEvents && mealEvents.length > 0 ? (
mealEvents.map((mealEvent, index) => (
<tr key={mealEvent.id}>
<td>{mealEvent.breakfast}</td>
<td>{mealEvent.lunch}</td>
<td>{mealEvent.snack}</td>
</tr>
))
) : (
<tr>
<td colSpan="3" className="text-center">No meal plan for today</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</Tab>
</Tabs>
)}
{/* Full Screen Content */}
{isFullScreen && (
<div className="multi-columns-container">
{canViewAppointmentCalendar && (
<div className="column-container">
<div className="column-card">
<h6 className="text-black fullscreen-title">Appointments</h6>
<table className="personnel-info-table info-screen-appointments-table">
<thead>
<tr>
<th>Time</th>
<th>Customer</th>
<th>Driver</th>
</tr>
</thead>
<tbody>
{medicalEvents && medicalEvents.length > 0 ? (
medicalEvents.map((medicalEvent, index) => (
<tr key={medicalEvent.id}>
<td>{medicalEvent.startTime}</td>
<td>{medicalEvent.customer}</td>
<td>{medicalEvent.driverInfo}</td>
</tr>
))
) : (
<tr>
<td colSpan="3" className="text-center">No medical appointments scheduled for today</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
)}
{/* Column 2 - Time, Weather, Attendance Note, Gallery */}
<div className="column-container">
{/* Time and Weather Cards Row */}
<div className="time-weather-row">
{/* Time Card */}
<div className="col-md-6">
<div className="card h-100">
<div className="card-body text-center">
<div className="fullscreen-date-display">
<div className="text-black">{moment(currentTime).format('dddd')}</div>
<div className="text-black">{moment(currentTime).format('MMM Do, YYYY')}</div>
<h5 className="text-primary mt-2 fullscreen-time">{moment(currentTime).format('HH:mm')}</h5>
</div>
<div className="clock-container">
<div className="clock">
<div className="clock-face">
<div className="hand hour-hand" style={{
transform: `rotate(${(moment(currentTime).hour() % 12) * 30 + moment(currentTime).minute() * 0.5}deg)`
}}></div>
<div className="hand minute-hand" style={{
transform: `rotate(${moment(currentTime).minute() * 6}deg)`
}}></div>
<div className="hand second-hand" style={{
transform: `rotate(${moment(currentTime).second() * 6}deg)`
}}></div>
<div className="center-dot"></div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Weather Card */}
<div className="col-md-6">
<div className="card h-100">
<div className="card-body text-center">
<div className="fullscreen-date-display">
<div className="text-black">New York, NY</div>
<div className="text-black">Partly Cloudy</div>
<h5 className="text-primary mt-2 fullscreen-time">22°C</h5>
</div>
<div className="weather-icon">
<i className="fas fa-cloud-sun"></i>
</div>
</div>
</div>
</div>
</div>
{/* AttendanceNote Card */}
<div className="attendance-note-wrapper">
<div className="card">
<div className="card-body">
<div className="d-flex justify-content-between align-items-start mb-2">
<div className="d-flex align-items-center">
<img src="/images/logo-trans.png" alt="Worldshine Logo" className="fullscreen-logo" />
<strong className="logo-worldshine">Worldshine</strong>
</div>
</div>
<div className="row">
<div className="col-md-8">
<h5 className="text-primary mb-2">{attendanceNote.slogan}</h5>
<p className="text-muted fullscreen-intro">{attendanceNote.introduction}</p>
</div>
<div className="col-md-4">
{attendanceNote.image ? (
<img src={attendanceNote.image} alt="Attendance Note" className="img-fluid rounded fullscreen-note-img" />
) : (
<div className="d-flex align-items-center justify-content-center fullscreen-placeholder">
<span className="text-muted">No image</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* Gallery Card */}
<div className="gallery-wrapper">
<div className="card">
<div className="card-body">
<h6 className="text-black mb-2 fullscreen-title">Gallery</h6>
{carousel.images.length > 0 ? (
<Carousel
interval={carousel.images.length > 1 ? 5000 : null}
controls={false}
indicators={carousel.images.length > 1}
className="fullscreen-carousel"
>
{carousel.images.map((image, index) => (
<Carousel.Item key={index}>
<img className="d-block w-100 fullscreen-carousel-img" src={image} alt={`Gallery ${index + 1}`} />
</Carousel.Item>
))}
</Carousel>
) : (
<div className="d-flex align-items-center justify-content-center fullscreen-placeholder">
<span className="text-muted">No images in gallery</span>
</div>
)}
</div>
</div>
</div>
</div>
{/* Column 3 - Activities and Menu */}
<div className="column-container">
<div className="column-card">
<h6 className="text-black fullscreen-title">Activities</h6>
<table className="personnel-info-table info-screen-appointments-table">
<thead>
<tr>
<th>Time</th>
<th>Activity</th>
<th>Location</th>
</tr>
</thead>
<tbody>
{activityEvents && activityEvents.length > 0 ? (
activityEvents.map((activityEvent, index) => (
<tr key={activityEvent.id}>
<td>{activityEvent.startTime}</td>
<td>{activityEvent.title}</td>
<td>{activityEvent.event_location}</td>
</tr>
))
) : (
<tr>
<td colSpan="3" className="text-center">No activities scheduled for today</td>
</tr>
)}
</tbody>
</table>
</div>
<div className="column-card">
<h6 className="text-black fullscreen-title">Menu</h6>
<table className="personnel-info-table info-screen-appointments-table">
<thead>
<tr>
<th>Breakfast</th>
<th>Lunch</th>
<th>Snack</th>
</tr>
</thead>
<tbody>
{mealEvents && mealEvents.length > 0 ? (
mealEvents.map((mealEvent, index) => (
<tr key={mealEvent.id}>
<td>{mealEvent.breakfast}</td>
<td>{mealEvent.lunch}</td>
<td>{mealEvent.snack}</td>
</tr>
))
) : (
<tr>
<td colSpan="3" className="text-center">No meal plan for today</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
)}
{!isFullScreen && (
<div className="list-func-panel">
<button className="btn btn-primary me-2" onClick={handleBackgroundSelect}>
<FileImage size={16} className="me-2"></FileImage>Choose Background
</button>
<button className="btn btn-primary me-2" onClick={handleFullScreen}>
<ArrowsFullscreen size={16} className="me-2"></ArrowsFullscreen>Full Screen
</button>
</div>
)}
</div>
</div>
{/* Edit AttendanceNote Modal */}
<Modal show={showEditModal} onHide={handleCloseModal} size="lg">
<Modal.Header closeButton>
<Modal.Title>Edit Attendance Note</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="row">
{/* Left Side - Text Fields */}
<div className="col-md-6">
<div className="mb-3">
<label className="form-label">Slogan</label>
<input
type="text"
className="form-control"
value={editForm.slogan}
onChange={(e) => handleFormChange('slogan', e.target.value)}
placeholder="Enter slogan"
/>
</div>
<div className="mb-3">
<label className="form-label">Introduction</label>
<textarea
className="form-control"
rows="4"
value={editForm.introduction}
onChange={(e) => handleFormChange('introduction', e.target.value)}
placeholder="Enter introduction text"
/>
</div>
</div>
{/* Right Side - File Upload */}
<div className="col-md-6">
<div className="mb-3">
<label className="form-label">Image</label>
<div
className="border rounded p-4 text-center"
style={{
border: '2px dashed #dee2e6',
backgroundColor: '#f8f9fa',
cursor: 'pointer'
}}
onClick={() => document.getElementById('imageUpload').click()}
>
{editForm.image ? (
<div>
<img
src={editForm.image}
alt="Preview"
className="img-fluid rounded mb-2"
style={{ maxHeight: '150px' }}
/>
<p className="text-muted mb-0">Click to change image</p>
</div>
) : (
<div>
<i className="fas fa-cloud-upload-alt" style={{ fontSize: '48px', color: '#6c757d' }}></i>
<p className="text-muted mb-0">Click to upload</p>
</div>
)}
<input
id="imageUpload"
type="file"
accept="image/*"
onChange={handleFileUpload}
style={{ display: 'none' }}
/>
</div>
</div>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleCloseModal}>
Cancel
</Button>
<Button variant="primary" onClick={handleSave}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
{/* Gallery Edit Modal */}
<Modal show={showGalleryModal} onHide={handleGalleryClose} size="lg">
<Modal.Header closeButton>
<Modal.Title>Update Images</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="mb-3">
<label className="form-label">Upload Images</label>
<div
className="border rounded p-4 text-center"
style={{
border: '2px dashed #dee2e6',
backgroundColor: '#f8f9fa',
cursor: 'pointer'
}}
onClick={() => document.getElementById('galleryUpload').click()}
>
<i className="fas fa-cloud-upload-alt" style={{ fontSize: '48px', color: '#6c757d' }}></i>
<p className="text-muted mb-0">Click to upload multiple images</p>
<input
id="galleryUpload"
type="file"
accept="image/*"
multiple
onChange={handleCarouselFileUpload}
style={{ display: 'none' }}
/>
</div>
</div>
{/* Current Images */}
{galleryForm.length > 0 && (
<div className="mb-3">
<label className="form-label">Current Images</label>
<div className="row">
{galleryForm.map((image, index) => (
<div key={index} className="col-md-4 mb-2">
<div className="position-relative">
<img
src={image}
alt={`Gallery ${index + 1}`}
className="img-fluid rounded"
style={{ height: '100px', width: '100%', objectFit: 'cover' }}
/>
<button
type="button"
className="btn btn-sm btn-danger position-absolute"
style={{ top: '5px', right: '5px' }}
onClick={() => removeCarouselImage(index)}
>
×
</button>
</div>
</div>
))}
</div>
</div>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleGalleryClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleCarouselSave}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
{/* Background Selection Modal */}
<Modal show={showBackgroundModal} onHide={handleBackgroundClose} size="md">
<Modal.Header closeButton>
<Modal.Title>Choose Background Image</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="mb-3">
<label className="form-label">Upload Background Image</label>
<div
className="border rounded p-4 text-center"
style={{
border: '2px dashed #dee2e6',
backgroundColor: '#f8f9fa',
cursor: 'pointer'
}}
onClick={() => document.getElementById('backgroundUpload').click()}
>
{selectedBackgroundFile ? (
<div>
<img
src={backgroundImage}
alt="Background Preview"
className="img-fluid rounded mb-2"
style={{ maxHeight: '200px', maxWidth: '100%' }}
/>
<p className="text-muted mb-0">Click to change background image</p>
</div>
) : (
<div>
<i className="fas fa-image" style={{ fontSize: '48px', color: '#6c757d' }}></i>
<p className="text-muted mb-0">Click to upload background image</p>
</div>
)}
<input
id="backgroundUpload"
type="file"
accept="image/*"
onChange={handleBackgroundFileUpload}
style={{ display: 'none' }}
/>
</div>
</div>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleBackgroundClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleBackgroundSave}>
Save Background
</Button>
</Modal.Footer>
</Modal>
{/* Full Screen Hint */}
{isFullScreen && (
<div className="fullscreen-hint">
Press ESC to exit full screen
</div>
)}
</>
);
};
export default InfoScreen;