Files
Kol-Manager/resources/views/files/index.blade.php
I-Fun 22f03ccca7 Add contract management and invoice settings pages
- Created a new Blade view for managing brand contracts with forms for adding and editing contracts, including client and category selection.
- Implemented success message notifications for contract actions.
- Added a new Blade view for invoice settings, allowing customization of invoice number format, header branding, and default values for new invoices.
- Introduced a new PDF view for generating rate cards with customizable styles based on user profile settings.
- Developed a profile management page for rate cards, enabling users to update their identity, appearance, and visibility settings.
2026-04-01 21:47:34 +07:00

230 lines
9.8 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', 'Files')
@section('page-title', 'File Manager 📁')
@section('page-subtitle', 'Brand assets, logos, and media files')
@section('content')
{{-- ── Upload Form (collapsible) ────────────────────────────── --}}
<div class="cat-card p-4 mb-4"
x-data="{
open: false,
uploading: false,
progress: 0,
statusMsg: '',
statusOk: true,
submitUpload(form) {
const fd = new FormData(form);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
this.progress = Math.round((e.loaded / e.total) * 100);
}
});
xhr.addEventListener('load', () => {
this.uploading = false;
if (xhr.status >= 200 && xhr.status < 300) {
this.statusOk = true;
this.statusMsg = '✅ File uploaded successfully!';
this.progress = 100;
form.reset();
setTimeout(() => {
this.open = false;
this.statusMsg = '';
this.progress = 0;
window.location.reload();
}, 1200);
} else {
this.statusOk = false;
try {
const body = JSON.parse(xhr.responseText);
this.statusMsg = body.message ?? 'Upload failed. Please try again.';
} catch {
this.statusMsg = 'Upload failed. Please try again.';
}
}
});
xhr.addEventListener('error', () => {
this.uploading = false;
this.statusOk = false;
this.statusMsg = 'Network error. Please try again.';
});
xhr.open('POST', form.action);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'application/json');
this.uploading = true;
this.progress = 0;
this.statusMsg = '';
xhr.send(fd);
}
}">
<div class="flex items-center justify-between">
<h3 class="text-sm font-bold text-gray-700 flex items-center gap-2">📤 Upload File</h3>
<button class="btn-sakura text-xs px-4 py-1.5" @click="open = !open; statusMsg = ''; progress = 0;"
x-text="open ? '✕ Close' : '+ Upload'"></button>
</div>
<div x-show="open" x-transition>
<form action="{{ route('files.store') }}" method="POST" enctype="multipart/form-data"
class="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-3"
@submit.prevent="submitUpload($el)">
@csrf
<div class="sm:col-span-3">
<label class="block text-xs font-semibold text-gray-600 mb-1">
File <span class="text-pink-400">*</span>
<span class="text-gray-400 font-normal ml-1">Max 50 MB</span>
</label>
<input type="file" name="file" required
class="w-full text-sm text-gray-600
file:mr-3 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-xs file:font-semibold
file:bg-pink-50 file:text-pink-600
hover:file:bg-pink-100
cursor-pointer">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Folder</label>
<input type="text" name="folder" value="general" class="input-soft text-sm"
list="folder-list" placeholder="brand-assets, logos...">
<datalist id="folder-list">
@foreach($folders as $f)
<option value="{{ $f }}">
@endforeach
<option value="brand-assets">
<option value="logos">
<option value="videos">
<option value="photos">
<option value="documents">
</datalist>
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Client (optional)</label>
<select name="client_id" class="input-soft text-sm">
<option value="">— None —</option>
@foreach($clients as $client)
<option value="{{ $client->id }}">{{ $client->name }}</option>
@endforeach
</select>
</div>
<div class="sm:flex sm:items-end">
<button type="submit" class="btn-sakura w-full py-2.5" :disabled="uploading">
<span x-show="!uploading">📤 Upload</span>
<span x-show="uploading">Uploading…</span>
</button>
</div>
</form>
{{-- Progress Bar --}}
<div x-show="uploading || progress > 0" x-transition class="mt-3">
<div class="flex items-center justify-between text-xs text-gray-500 mb-1">
<span>Uploading…</span>
<span x-text="progress + '%'"></span>
</div>
<div class="w-full bg-pink-100 rounded-full h-2.5 overflow-hidden">
<div class="h-2.5 rounded-full transition-all duration-200"
style="background:#FFB7C5"
:style="'width:' + progress + '%'"></div>
</div>
</div>
{{-- Status Message --}}
<div x-show="statusMsg" x-transition
class="mt-3 px-4 py-2.5 rounded-xl text-sm font-medium"
:class="statusOk ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-600'"
x-text="statusMsg">
</div>
</div>
</div>
{{-- ── Folder Filter Chips ──────────────────────────────────── --}}
<div class="flex gap-2 overflow-x-auto pb-2 mb-4 -mx-4 px-4 lg:mx-0 lg:px-0">
<a href="{{ route('files.index') }}"
class="shrink-0 px-3 py-1.5 rounded-full text-xs font-semibold transition-colors
{{ !request('folder') || request('folder') === 'all'
? 'text-white' : 'bg-white text-gray-500 border border-pink-100 hover:bg-pink-50' }}"
style="{{ !request('folder') || request('folder') === 'all' ? 'background:#FFB7C5' : '' }}">
All
</a>
@foreach($folders as $f)
<a href="{{ request()->fullUrlWithQuery(['folder' => $f]) }}"
class="shrink-0 px-3 py-1.5 rounded-full text-xs font-semibold capitalize transition-colors
{{ request('folder') === $f
? 'text-white' : 'bg-white text-gray-500 border border-pink-100 hover:bg-pink-50' }}"
style="{{ request('folder') === $f ? 'background:#FFB7C5' : '' }}">
{{ $f }}
</a>
@endforeach
</div>
{{-- ── File Grid ────────────────────────────────────────────── --}}
@forelse($files as $file)
@if($loop->first)
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3">
@endif
<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">
<img src="{{ $file->url }}" alt="{{ $file->name }}"
class="w-full h-full object-cover">
</div>
@else
<div class="w-full aspect-square rounded-2xl bg-pink-50 flex items-center justify-center text-4xl">
{{ $file->icon }}
</div>
@endif
{{-- Info --}}
<div class="min-w-0">
<p class="text-xs font-semibold text-gray-700 truncate" title="{{ $file->original_name }}">
{{ $file->name ?: $file->original_name }}
</p>
<p class="text-xs text-gray-400">{{ $file->human_size }}</p>
@if($file->client)
<p class="text-xs text-pink-500 truncate">👥 {{ $file->client->name }}</p>
@endif
<span class="text-xs bg-pink-50 text-pink-600 px-2 py-0.5 rounded-full capitalize inline-block mt-0.5">
{{ $file->folder }}
</span>
</div>
{{-- Actions --}}
<div class="flex gap-1.5 mt-auto">
<a href="{{ $file->url }}" target="_blank"
class="flex-1 text-center text-xs py-1.5 rounded-full border border-pink-200 text-pink-600 hover:bg-pink-50 font-medium">
View
</a>
<form action="{{ route('files.destroy', $file) }}" method="POST"
onsubmit="return confirm('Delete this file?')">
@csrf @method('DELETE')
<button class="text-xs py-1.5 px-2.5 rounded-full border border-red-100 text-red-400 hover:bg-red-50">
✕
</button>
</form>
</div>
</div>
@if($loop->last)
</div>
@endif
@empty
<div class="text-center py-16 text-gray-400">
<span class="text-5xl block mb-3">📁</span>
<p class="text-sm">No files yet.</p>
<button onclick="document.querySelector('[x-data]').__x.$data.open = true"
class="btn-sakura mt-4 inline-block text-sm">Upload your first file</button>
</div>
@endforelse
<div class="mt-4">{{ $files->links() }}</div>
@endsection