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:
@@ -8,12 +8,55 @@
|
||||
<div class="max-w-5xl mx-auto">
|
||||
|
||||
{{-- Profile Header --}}
|
||||
<div class="cat-card p-4 mb-4">
|
||||
<div class="cat-card p-4 mb-4"
|
||||
x-data="{
|
||||
avatarUrl: '{{ $client->avatar ? asset('storage/' . $client->avatar) : '' }}',
|
||||
uploading: false,
|
||||
async pickAvatar(input) {
|
||||
if (!input.files.length) return;
|
||||
this.uploading = true;
|
||||
const fd = new FormData();
|
||||
fd.append('avatar', input.files[0]);
|
||||
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
||||
const res = await fetch('{{ route('clients.upload-avatar', $client) }}', { method: 'POST', body: fd });
|
||||
const data = await res.json();
|
||||
if (res.ok) this.avatarUrl = data.url + '?t=' + Date.now();
|
||||
this.uploading = false;
|
||||
input.value = '';
|
||||
},
|
||||
async removeAvatar() {
|
||||
if (!confirm('Remove profile picture?')) return;
|
||||
const fd = new FormData();
|
||||
fd.append('_token', document.querySelector('meta[name=csrf-token]').content);
|
||||
fd.append('_method', 'DELETE');
|
||||
await fetch('{{ route('clients.delete-avatar', $client) }}', { method: 'POST', body: fd });
|
||||
this.avatarUrl = '';
|
||||
}
|
||||
}">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-14 h-14 rounded-3xl flex items-center justify-center text-white font-bold text-xl shadow shrink-0"
|
||||
style="background: linear-gradient(135deg, #FFB7C5, #E8D5F5);">
|
||||
{{ $client->initials }}
|
||||
{{-- Avatar with upload overlay --}}
|
||||
<div class="relative shrink-0 group">
|
||||
<template x-if="avatarUrl">
|
||||
<img :src="avatarUrl" alt="{{ $client->name }}"
|
||||
class="w-16 h-16 rounded-3xl object-cover shadow border border-pink-100">
|
||||
</template>
|
||||
<template x-if="!avatarUrl">
|
||||
<div class="w-16 h-16 rounded-3xl flex items-center justify-center text-white font-bold text-xl shadow"
|
||||
style="background: linear-gradient(135deg, #FFB7C5, #E8D5F5);">
|
||||
{{ $client->initials }}
|
||||
</div>
|
||||
</template>
|
||||
{{-- Upload overlay --}}
|
||||
<label class="absolute inset-0 rounded-3xl bg-black/30 flex items-center justify-center
|
||||
opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
|
||||
:class="{ 'opacity-100': uploading }"
|
||||
title="Change photo">
|
||||
<span x-show="!uploading" class="text-white text-lg">📷</span>
|
||||
<span x-show="uploading" class="text-white text-xs">…</span>
|
||||
<input type="file" accept="image/*" class="hidden" @change="pickAvatar($event.target)">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h2 class="text-lg font-bold text-gray-800 leading-tight">{{ $client->name }}</h2>
|
||||
@if($client->company) <p class="text-sm text-gray-500">{{ $client->company }}</p> @endif
|
||||
@@ -31,13 +74,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- Action buttons below on mobile, inline on larger screens --}}
|
||||
<div class="flex gap-2 mt-3">
|
||||
|
||||
<div class="flex gap-2 mt-3 flex-wrap">
|
||||
<a href="{{ route('clients.edit', $client) }}" class="btn-sakura text-sm px-4 py-2">Edit</a>
|
||||
<a href="{{ route('invoices.create') }}?client={{ $client->id }}"
|
||||
class="text-sm px-4 py-2 rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50 font-semibold">
|
||||
🧾 New Invoice
|
||||
</a>
|
||||
<button x-show="avatarUrl" @click="removeAvatar()"
|
||||
class="text-sm px-4 py-2 rounded-full border border-red-200 text-red-400 hover:bg-red-50 font-semibold">
|
||||
🗑️ Remove Photo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user