Some checks failed
Build And Deploy Main / build-and-deploy (push) Has been cancelled
1408 lines
56 KiB
JavaScript
1408 lines
56 KiB
JavaScript
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 canEditInfoScreen = AuthService.canEditInfoScreen();
|
||
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>
|
||
{canEditInfoScreen && (
|
||
<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>
|
||
{canEditInfoScreen && (
|
||
<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">
|
||
{canEditInfoScreen && (
|
||
<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 */}
|
||
{canEditInfoScreen && <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 */}
|
||
{canEditInfoScreen && <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 */}
|
||
{canEditInfoScreen && <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;
|