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.
This commit is contained in:
@@ -6,60 +6,141 @@
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- ── Upload Form (collapsible on mobile) ─────────────────── --}}
|
||||
<div class="cat-card p-4 mb-4" x-data="{ open: false }">
|
||||
{{-- ── 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"
|
||||
<button class="btn-sakura text-xs px-4 py-1.5" @click="open = !open; statusMsg = ''; progress = 0;"
|
||||
x-text="open ? '✕ Close' : '+ Upload'"></button>
|
||||
</div>
|
||||
|
||||
<form x-show="open" x-transition
|
||||
action="{{ route('files.store') }}" method="POST" enctype="multipart/form-data"
|
||||
class="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
@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 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>
|
||||
<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>
|
||||
|
||||
{{-- 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>
|
||||
<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">📤 Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Folder Filter Chips ──────────────────────────────────── --}}
|
||||
@@ -124,7 +205,7 @@
|
||||
<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-3 rounded-full border border-red-100 text-red-400 hover:bg-red-50">
|
||||
<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>
|
||||
@@ -138,7 +219,7 @@
|
||||
<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('[\\@click=\\'open = !open\\']').click()"
|
||||
<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
|
||||
|
||||
Reference in New Issue
Block a user