Files
Kol-Manager/resources/views/schedule/index.blade.php
2026-04-05 09:56:29 +07:00

246 lines
14 KiB
PHP
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.
@extends('layouts.app')
@section('title', 'Schedule')
@section('page-title', 'Schedule & Calendar 📅')
@section('page-subtitle', 'Tap a date or event to manage it')
@section('content')
{{-- ── Add Event Button ─────────────────────────────────────── --}}
<div class="flex items-center justify-between mb-4">
<a href="{{ route('schedule.create') }}" class="btn-sakura text-sm px-5 py-2">+ Add Event</a>
</div>
{{-- ── Upcoming Alert Strip ─────────────────────────────────── --}}
@php
$startOfMonth = now()->startOfMonth();
$endOfMonth = now()->endOfMonth();
$upcomingList = $events->filter(function($e) use ($startOfMonth, $endOfMonth) {
$start = $e->start_time;
$end = $e->end_time ?: $e->start_time;
return $start->lte($endOfMonth) && $end->gte($startOfMonth);
});
@endphp
@if($upcomingList->isNotEmpty())
<div class="flex gap-2 overflow-x-auto pb-2 mb-4 -mx-4 px-4 lg:mx-0 lg:px-0">
@foreach($upcomingList as $event)
<div class="shrink-0 flex items-center gap-2 bg-white rounded-2xl border border-pink-100 px-3 py-2 shadow-sm cursor-pointer hover:border-pink-300 transition-colors"
onclick="openAlertEventModal({{ json_encode(['id' => $event->id, 'title' => $event->title, 'type' => $event->event_type, 'status' => $event->status->value, 'client' => $event->client?->name, 'start' => $event->start_time->format('d M Y H:i'), 'end' => $event->end_time?->format('d M Y H:i'), 'location' => $event->location, 'description' => $event->description, 'notes' => $event->notes, 'has_contract' => $event->has_contract, 'has_content' => $event->has_content]) }})">
<div class="w-1.5 h-8 rounded-full shrink-0" style="background:{{ $event->color }}"></div>
<div>
<div class="text-xs font-semibold text-gray-700 whitespace-nowrap">{{ $event->title }}</div>
<div class="text-xs text-gray-400">{{ $event->start_time->format('H:i') }}</div>
</div>
</div>
@endforeach
</div>
@endif
{{-- ── Calendar ─────────────────────────────────────────────── --}}
<div class="cat-card p-3 lg:p-5">
<div id="calendar"></div>
</div>
{{-- ── Event Detail Modal ───────────────────────────────────── --}}
<div id="event-modal" class="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/40 hidden">
<div class="cat-card p-5 w-full sm:max-w-md rounded-b-none sm:rounded-3xl shadow-xl mx-0 sm:mx-4">
<div class="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-4 sm:hidden"></div>
<div class="flex items-center justify-between mb-3">
<h3 class="font-bold text-gray-800 text-base" id="modal-title">Event</h3>
<button onclick="document.getElementById('event-modal').classList.add('hidden')"
class="text-gray-400 hover:text-gray-600 text-xl leading-none">✕</button>
</div>
<div id="modal-body" class="text-sm text-gray-600 space-y-2 mb-4"></div>
<div id="modal-actions" class="flex gap-2"></div>
</div>
</div>
@push('scripts')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.css">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.js"></script>
<style>
/* ── FullCalendar mobile overrides ── */
.fc .fc-toolbar { flex-wrap: wrap; gap: 8px; }
.fc .fc-toolbar-title { font-size: 1rem; font-weight: 700; }
.fc .fc-button {
background: #FFB7C5 !important;
border-color: #FFB7C5 !important;
border-radius: 999px !important;
font-size: 0.75rem !important;
padding: 4px 12px !important;
box-shadow: none !important;
}
.fc .fc-button:hover { background: #FF8FAD !important; border-color: #FF8FAD !important; }
.fc .fc-button-active { background: #FF8FAD !important; border-color: #FF8FAD !important; }
.fc .fc-daygrid-event { border-radius: 8px !important; font-size: 0.7rem; }
.fc .fc-list-event:hover td { background: #FFF0F5 !important; }
.fc .fc-list-day-cushion { background: #FFF8F0 !important; }
@media (max-width: 639px) {
.fc .fc-toolbar { gap: 6px; }
.fc .fc-toolbar-title { font-size: 0.9rem; }
.fc .fc-button { padding: 3px 8px !important; font-size: 0.7rem !important; }
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
const isMobile = window.innerWidth < 640;
const calendar = new FullCalendar.Calendar(document.getElementById('calendar'), {
initialView: isMobile ? 'listWeek' : 'dayGridMonth',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: isMobile ? 'listWeek,timeGridDay' : 'dayGridMonth,timeGridWeek,listWeek',
},
editable: !isMobile,
selectable: true,
events: @json($calendarEvents),
height: 'auto',
windowResize(view) {
const mob = window.innerWidth < 640;
if (mob && calendar.view.type !== 'listWeek' && calendar.view.type !== 'timeGridDay') {
calendar.changeView('listWeek');
}
},
eventDrop(info) {
fetch(`/schedule/${info.event.id}/dates`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({
start_time: info.event.startStr ? info.event.startStr.substring(0, 19) : null,
end_time: info.event.endStr ? info.event.endStr.substring(0, 19) : null,
}),
}).then(r => r.ok ? window.KolManager.notify('Event rescheduled! 🐾') : info.revert());
},
select(info) {
// Pre-fill date from calendar click
const dateInput = document.getElementById('start_date');
if (dateInput) dateInput.value = info.startStr.substring(0, 10);
const endInput = document.getElementById('end_date');
if (endInput && info.endStr) endInput.value = info.endStr.substring(0, 10);
},
eventClick(info) {
const e = info.event, p = e.extendedProps;
document.getElementById('modal-title').textContent = e.title;
document.getElementById('modal-body').innerHTML = `
<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Type</span><span>${p.type}</span></div>
<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Status</span><span>${p.status}</span></div>
${p.client ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Client</span><span>${p.client}</span></div>` : ''}
${p.location ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Location</span><span>${p.location}</span></div>` : ''}
<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Start</span><span>${e.start?.toLocaleString()}</span></div>
${e.end ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">End</span><span>${e.end.toLocaleString()}</span></div>` : ''}
${p.description ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Description</span><div class="flex-1 break-words whitespace-pre-wrap overflow-y-auto max-h-20 pr-1">${p.description}</div></div>` : ''}
${p.notes ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Notes</span><div class="flex-1 break-words whitespace-pre-wrap overflow-y-auto max-h-20 pr-1">${p.notes}</div></div>` : ''}
${p.has_contract ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Contract</span><span class="text-pink-500 font-semibold">Yes</span></div>` : ''}
${p.has_content ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Content Bd</span><span class="text-pink-500 font-semibold">Yes</span></div>` : ''}
`;
const editUrl = `/schedule/${e.id}/edit`;
document.getElementById('modal-actions').innerHTML = `
<a href="${editUrl}" class="btn-sakura text-sm flex-1 text-center py-2">Edit</a>
<form method="POST" action="/schedule/${e.id}" onsubmit="return confirm('Delete?')" class="flex-1">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="${document.querySelector('meta[name=csrf-token]').content}">
<button class="w-full py-2 rounded-full border border-red-200 text-red-400 text-sm hover:bg-red-50">Delete</button>
</form>
`;
document.getElementById('event-modal').classList.remove('hidden');
},
});
calendar.render();
// Close modal on backdrop tap
document.getElementById('event-modal').addEventListener('click', function(e) {
if (e.target === this) this.classList.add('hidden');
});
// Note: datetime hidden fields are populated in contractCheck's submitIfClear()
// since @submit.prevent is used on the form (form.submit() bypasses native submit event)
});
function openAlertEventModal(e) {
document.getElementById('modal-title').textContent = e.title;
const startStr = e.start ? e.start : '—';
const endStr = e.end ? e.end : null;
document.getElementById('modal-body').innerHTML = `
<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Type</span><span>${e.type}</span></div>
<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Status</span><span>${e.status}</span></div>
${e.client ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Client</span><span>${e.client}</span></div>` : ''}
${e.location ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Location</span><span>${e.location}</span></div>` : ''}
<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Start</span><span>${startStr}</span></div>
${endStr ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">End</span><span>${endStr}</span></div>` : ''}
${e.description ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Description</span><div class="flex-1 break-words whitespace-pre-wrap overflow-y-auto max-h-20 pr-1">${e.description}</div></div>` : ''}
${e.notes ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Notes</span><div class="flex-1 break-words whitespace-pre-wrap overflow-y-auto max-h-20 pr-1">${e.notes}</div></div>` : ''}
${e.has_contract ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Contract</span><span class="text-pink-500 font-semibold">Yes</span></div>` : ''}
${e.has_content ? `<div class="flex gap-2"><span class="text-gray-400 w-24 shrink-0">Content Bd</span><span class="text-pink-500 font-semibold">Yes</span></div>` : ''}
`;
document.getElementById('modal-actions').innerHTML = `
<a href="/schedule/${e.id}/edit" class="btn-sakura text-sm flex-1 text-center py-2">Edit</a>
<form method="POST" action="/schedule/${e.id}" onsubmit="return confirm('Delete?')" class="flex-1">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="${document.querySelector('meta[name=csrf-token]').content}">
<button class="w-full py-2 rounded-full border border-red-200 text-red-400 text-sm hover:bg-red-50">Delete</button>
</form>
`;
document.getElementById('event-modal').classList.remove('hidden');
}
</script>
{{-- Share scripts disabled --}}
<script>
function contractCheck(checkUrl) {
return {
categoryId: '',
clientId: '',
contractWarning: false,
contractMessage: '',
async checkContract() {
if (!this.categoryId) { this.contractWarning = false; return; }
const params = new URLSearchParams({ category_id: this.categoryId, client_id: this.clientId || '' });
const res = await fetch(checkUrl + '?' + params);
const data = await res.json();
if (data) {
this.contractWarning = true;
this.contractMessage = `"${data.title}" — ${data.client_name} holds exclusive rights in the ${data.category} category until ${data.end_date} (${data.days_left} days left).`;
} else {
this.contractWarning = false;
this.contractMessage = '';
}
},
submitIfClear(form) {
if (this.contractWarning) {
if (!confirm('⚠️ An exclusive contract is active for this category.\n\nAre you sure you want to proceed?')) return;
}
// Combine date + time inputs into the hidden datetime fields
// (form.submit() bypasses native 'submit' event, so we do it here)
const startDate = document.getElementById('start_date').value;
const startTime = document.getElementById('start_time_input').value || '00:00';
const endDate = document.getElementById('end_date').value;
const endTime = document.getElementById('end_time_input').value || '00:00';
if (!startDate) {
alert('Please select a start date.');
return;
}
document.getElementById('start_time_hidden').value = startDate + 'T' + startTime;
if (endDate) {
document.getElementById('end_time_hidden').value = endDate + 'T' + endTime;
}
form.submit();
}
};
}
</script>
@endpush
@endsection