feat: add notes management feature with CRUD operations

- Created Note model and controller for handling notes.
- Added routes for notes CRUD operations.
- Implemented notes index view with search and pinning functionality.
- Created migration for notes table with necessary fields.
- Developed event creation form with client and contract options.
This commit is contained in:
I-Fun
2026-04-02 23:24:38 +07:00
parent 5748b163b6
commit 1b90f730a6
25 changed files with 1524 additions and 390 deletions

View File

@@ -6,6 +6,29 @@
@section('content')
{{-- ── Video Player Modal ───────────────────────────────────── --}}
<div id="video-player-modal"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 hidden"
onclick="if(event.target===this)closeVideoModal()">
<div class="relative w-full max-w-3xl mx-4">
<div class="flex items-center justify-between mb-3 px-1">
<p class="text-white text-sm font-semibold truncate" id="vpm-title"></p>
<button onclick="closeVideoModal()"
class="text-white/70 hover:text-white text-2xl leading-none ml-4 shrink-0">✕</button>
</div>
<video id="vpm-player"
class="w-full rounded-2xl bg-black shadow-2xl"
controls controlsList="nodownload"
style="max-height:70vh">
</video>
<div class="flex items-center justify-between mt-3 px-1 text-xs text-white/60">
<span id="vpm-meta"></span>
<a id="vpm-dl" href="#" target="_blank"
class="text-pink-300 hover:text-pink-200 font-medium">⬇️ Download</a>
</div>
</div>
</div>
{{-- ── File Detail Popup Modal ──────────────────────────────── --}}
<div id="file-detail-modal"
class="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/40 hidden"
@@ -191,17 +214,37 @@
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
@endif
@php $fileData = ['name' => $file->name ?: $file->original_name, 'url' => $file->url, 'size' => $file->human_size, 'folder' => $file->folder, 'client' => $file->client?->name, 'is_image' => $file->is_image, 'is_video' => $file->is_video, 'icon' => $file->icon, 'created_at' => $file->created_at->format('d M Y')] @endphp
<div class="cat-card p-3 flex flex-col gap-2">
{{-- Preview / Icon --}}
@if($file->is_image)
<div class="w-full aspect-square rounded-2xl overflow-hidden bg-pink-50 cursor-pointer"
onclick="showFileDetail({{ json_encode(['name' => $file->name ?: $file->original_name, 'url' => $file->url, 'size' => $file->human_size, 'folder' => $file->folder, 'client' => $file->client?->name, 'is_image' => $file->is_image, 'icon' => $file->icon, 'created_at' => $file->created_at->format('d M Y')]) }})">
onclick="showFileDetail({{ json_encode($fileData) }})">
<img src="{{ $file->url }}" alt="{{ $file->name }}"
class="w-full h-full object-cover">
</div>
@elseif($file->is_video)
<div class="w-full aspect-square rounded-2xl overflow-hidden bg-gray-900 relative cursor-pointer group"
onclick="openVideoPlayer({{ json_encode($fileData) }})">
{{-- Native video poster (browser generates a frame) --}}
<video src="{{ $file->url }}"
class="w-full h-full object-cover opacity-80"
preload="metadata"
muted playsinline
style="pointer-events:none">
</video>
{{-- Play overlay --}}
<div class="absolute inset-0 flex items-center justify-center
bg-black/20 group-hover:bg-black/40 transition-colors">
<div class="w-12 h-12 rounded-full bg-white/90 flex items-center justify-center shadow-lg
group-hover:scale-110 transition-transform">
<span class="text-gray-800 text-xl ml-0.5">▶</span>
</div>
</div>
</div>
@else
<div class="w-full aspect-square rounded-2xl bg-pink-50 flex items-center justify-center text-4xl cursor-pointer"
onclick="showFileDetail({{ json_encode(['name' => $file->name ?: $file->original_name, 'url' => $file->url, 'size' => $file->human_size, 'folder' => $file->folder, 'client' => $file->client?->name, 'is_image' => false, 'icon' => $file->icon, 'created_at' => $file->created_at->format('d M Y')]) }})">
onclick="showFileDetail({{ json_encode($fileData) }})">
{{ $file->icon }}
</div>
@endif
@@ -256,7 +299,6 @@ function showFileDetail(file) {
document.getElementById('fdm-name').textContent = file.name;
document.getElementById('fdm-view-btn').href = file.url;
// Preview area
const preview = document.getElementById('fdm-preview');
if (file.is_image) {
preview.innerHTML = `<img src="${file.url}" alt="${file.name}" class="max-h-48 rounded-2xl object-contain border border-pink-100">`;
@@ -264,7 +306,6 @@ function showFileDetail(file) {
preview.innerHTML = `<span class="text-6xl">${file.icon}</span>`;
}
// Info rows
const rows = [
['Size', file.size],
['Folder', file.folder],
@@ -278,6 +319,32 @@ function showFileDetail(file) {
document.getElementById('file-detail-modal').classList.remove('hidden');
}
function openVideoPlayer(file) {
const player = document.getElementById('vpm-player');
player.src = file.url;
player.load();
document.getElementById('vpm-title').textContent = file.name;
document.getElementById('vpm-meta').textContent = file.size + (file.client ? ' · ' + file.client : '');
document.getElementById('vpm-dl').href = file.url;
document.getElementById('video-player-modal').classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
function closeVideoModal() {
const player = document.getElementById('vpm-player');
player.pause();
player.src = '';
document.getElementById('video-player-modal').classList.add('hidden');
document.body.style.overflow = '';
}
// Close video modal on Escape
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closeVideoModal();
});
</script>
@endpush