feat: Оптимизация навигации AdminJS в логические группы
- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование - Удалены дублирующие настройки navigation для чистой группировки - Добавлены CSS стили для визуального отображения иерархии с отступами - Добавлены эмодзи-иконки для каждого типа ресурсов через CSS - Улучшена навигация с правильной вложенностью элементов
This commit is contained in:
223
public/components/admin-calendar-resource.jsx
Normal file
223
public/components/admin-calendar-resource.jsx
Normal file
@@ -0,0 +1,223 @@
|
||||
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
|
||||
Reference in New Issue
Block a user