- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование - Удалены дублирующие настройки navigation для чистой группировки - Добавлены CSS стили для визуального отображения иерархии с отступами - Добавлены эмодзи-иконки для каждого типа ресурсов через CSS - Улучшена навигация с правильной вложенностью элементов
223 lines
7.9 KiB
JavaScript
223 lines
7.9 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
||
|
||
const AdminCalendarResource = () => {
|
||
const [currentDate, setCurrentDate] = useState(new Date())
|
||
const [guides, setGuides] = useState([])
|
||
const [selectedGuide, setSelectedGuide] = useState(null)
|
||
const [workingDays, setWorkingDays] = useState([])
|
||
const [holidays, setHolidays] = useState([])
|
||
const [loading, setLoading] = useState(true)
|
||
|
||
useEffect(() => {
|
||
loadData()
|
||
}, [currentDate])
|
||
|
||
const loadData = async () => {
|
||
setLoading(true)
|
||
try {
|
||
const [guidesRes, holidaysRes] = await Promise.all([
|
||
fetch('/api/guides'),
|
||
fetch('/api/holidays')
|
||
])
|
||
|
||
const guidesData = await guidesRes.json()
|
||
const holidaysData = await holidaysRes.json()
|
||
|
||
setGuides(guidesData.data || guidesData)
|
||
setHolidays(holidaysData)
|
||
|
||
if (selectedGuide) {
|
||
const workingRes = await fetch(`/api/guide-working-days?guide_id=${selectedGuide}&month=${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}`)
|
||
const workingData = await workingRes.json()
|
||
setWorkingDays(workingData)
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading data:', error)
|
||
}
|
||
setLoading(false)
|
||
}
|
||
|
||
const getDaysInMonth = (date) => {
|
||
const year = date.getFullYear()
|
||
const month = date.getMonth()
|
||
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
||
const firstDayOfWeek = new Date(year, month, 1).getDay()
|
||
|
||
const days = []
|
||
|
||
// Добавляем пустые дни в начале
|
||
for (let i = 0; i < (firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1); i++) {
|
||
days.push(null)
|
||
}
|
||
|
||
// Добавляем дни месяца
|
||
for (let day = 1; day <= daysInMonth; day++) {
|
||
days.push(day)
|
||
}
|
||
|
||
return days
|
||
}
|
||
|
||
const isWorkingDay = (day) => {
|
||
if (!day || !selectedGuide) return false
|
||
const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
||
return workingDays.some(wd => wd.work_date === dateStr)
|
||
}
|
||
|
||
const isHoliday = (day) => {
|
||
if (!day) return false
|
||
const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
||
return holidays.some(h => h.date === dateStr)
|
||
}
|
||
|
||
const toggleWorkingDay = async (day) => {
|
||
if (!selectedGuide || !day) return
|
||
|
||
const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
||
const isWorking = isWorkingDay(day)
|
||
|
||
try {
|
||
if (isWorking) {
|
||
await fetch('/api/guide-working-days', {
|
||
method: 'DELETE',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ guide_id: selectedGuide, work_date: dateStr })
|
||
})
|
||
} else {
|
||
await fetch('/api/guide-working-days', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ guide_id: selectedGuide, work_date: dateStr })
|
||
})
|
||
}
|
||
loadData()
|
||
} catch (error) {
|
||
console.error('Error toggling working day:', error)
|
||
}
|
||
}
|
||
|
||
const changeMonth = (delta) => {
|
||
const newDate = new Date(currentDate)
|
||
newDate.setMonth(newDate.getMonth() + delta)
|
||
setCurrentDate(newDate)
|
||
}
|
||
|
||
const monthNames = [
|
||
'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
|
||
'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
|
||
]
|
||
|
||
const weekDays = ['ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ', 'ВС']
|
||
|
||
if (loading) {
|
||
return (
|
||
<div style={{ padding: '20px', textAlign: 'center' }}>
|
||
<div>Загрузка календаря...</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div style={{ padding: '20px', fontFamily: 'system-ui' }}>
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<h2>Календарь рабочих дней гидов</h2>
|
||
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<label style={{ display: 'block', marginBottom: '5px' }}>Выберите гида:</label>
|
||
<select
|
||
value={selectedGuide || ''}
|
||
onChange={(e) => setSelectedGuide(e.target.value)}
|
||
style={{ padding: '8px', borderRadius: '4px', border: '1px solid #ddd', minWidth: '200px' }}
|
||
>
|
||
<option value="">-- Выберите гида --</option>
|
||
{guides.map(guide => (
|
||
<option key={guide.id} value={guide.id}>{guide.name}</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '20px', marginBottom: '20px' }}>
|
||
<button
|
||
onClick={() => changeMonth(-1)}
|
||
style={{ padding: '8px 16px', border: '1px solid #ddd', borderRadius: '4px', background: 'white', cursor: 'pointer' }}
|
||
>
|
||
← Предыдущий
|
||
</button>
|
||
<h3 style={{ margin: 0 }}>
|
||
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
|
||
</h3>
|
||
<button
|
||
onClick={() => changeMonth(1)}
|
||
style={{ padding: '8px 16px', border: '1px solid #ddd', borderRadius: '4px', background: 'white', cursor: 'pointer' }}
|
||
>
|
||
Следующий →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{selectedGuide && (
|
||
<div>
|
||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '1px', marginBottom: '20px' }}>
|
||
{weekDays.map(day => (
|
||
<div key={day} style={{
|
||
padding: '10px',
|
||
textAlign: 'center',
|
||
fontWeight: 'bold',
|
||
background: '#f5f5f5',
|
||
border: '1px solid #ddd'
|
||
}}>
|
||
{day}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '1px' }}>
|
||
{getDaysInMonth(currentDate).map((day, index) => (
|
||
<div
|
||
key={index}
|
||
onClick={() => toggleWorkingDay(day)}
|
||
style={{
|
||
padding: '15px',
|
||
textAlign: 'center',
|
||
border: '1px solid #ddd',
|
||
minHeight: '50px',
|
||
cursor: day ? 'pointer' : 'default',
|
||
background: day ?
|
||
(isHoliday(day) ? '#ffcccb' :
|
||
isWorkingDay(day) ? '#c8e6c9' : 'white') : '#f9f9f9',
|
||
color: day ? (isHoliday(day) ? '#d32f2f' : '#333') : '#ccc',
|
||
fontWeight: day ? 'normal' : '300'
|
||
}}
|
||
>
|
||
{day || ''}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div style={{ marginTop: '20px', display: 'flex', gap: '20px', fontSize: '14px' }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||
<div style={{ width: '20px', height: '20px', background: '#c8e6c9', border: '1px solid #ddd' }}></div>
|
||
<span>Рабочий день</span>
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||
<div style={{ width: '20px', height: '20px', background: '#ffcccb', border: '1px solid #ddd' }}></div>
|
||
<span>Выходной/Праздник</span>
|
||
</div>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
|
||
<div style={{ width: '20px', height: '20px', background: 'white', border: '1px solid #ddd' }}></div>
|
||
<span>Не назначено</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{!selectedGuide && (
|
||
<div style={{ textAlign: 'center', padding: '40px', color: '#666' }}>
|
||
Выберите гида для просмотра календаря
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default AdminCalendarResource |