feat: Оптимизация навигации AdminJS в логические группы

- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование
- Удалены дублирующие настройки navigation для чистой группировки
- Добавлены CSS стили для визуального отображения иерархии с отступами
- Добавлены эмодзи-иконки для каждого типа ресурсов через CSS
- Улучшена навигация с правильной вложенностью элементов
This commit is contained in:
2025-11-30 21:57:58 +09:00
parent 1e7d7c06eb
commit 13c752b93a
47 changed files with 14148 additions and 61 deletions

View 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