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:
@@ -7,8 +7,10 @@ namespace App\Http\Controllers;
|
||||
use App\Enums\ClientStatus;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientCategory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ClientController extends Controller
|
||||
@@ -146,4 +148,42 @@ class ClientController extends Controller
|
||||
return redirect()->route('clients.index')
|
||||
->with('success', '🐱 Client removed.');
|
||||
}
|
||||
|
||||
public function quickStore(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'nullable|email|unique:clients,email',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'company' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$client = Client::create(array_merge($validated, ['status' => 'active']));
|
||||
|
||||
return response()->json(['id' => $client->id, 'name' => $client->name]);
|
||||
}
|
||||
|
||||
public function uploadAvatar(Request $request, Client $client): JsonResponse
|
||||
{
|
||||
$request->validate(['avatar' => 'required|image|max:2048']);
|
||||
|
||||
if ($client->avatar) {
|
||||
Storage::disk('public')->delete($client->avatar);
|
||||
}
|
||||
|
||||
$path = $request->file('avatar')->store('client-avatars', 'public');
|
||||
$client->update(['avatar' => $path]);
|
||||
|
||||
return response()->json(['url' => asset('storage/' . $path)]);
|
||||
}
|
||||
|
||||
public function deleteAvatar(Client $client): JsonResponse
|
||||
{
|
||||
if ($client->avatar) {
|
||||
Storage::disk('public')->delete($client->avatar);
|
||||
$client->update(['avatar' => null]);
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\ContentStatus;
|
||||
use App\Enums\EventStatus;
|
||||
use App\Models\BrandContract;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientCategory;
|
||||
use App\Models\ContentBrief;
|
||||
use App\Models\Event;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@@ -25,6 +27,14 @@ class EventController extends Controller
|
||||
->get();
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
$clients = Client::where('status', 'active')->orderBy('name')->get();
|
||||
$categoryTree = $this->categoryTree();
|
||||
|
||||
return view('schedule.create', compact('clients', 'categoryTree'));
|
||||
}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$events = Event::with('client')->orderBy('start_time')->get();
|
||||
@@ -69,6 +79,9 @@ class EventController extends Controller
|
||||
'contract_title' => 'nullable|string|max:255',
|
||||
'contract_start_date' => 'nullable|date',
|
||||
'contract_end_date' => 'nullable|date|after_or_equal:contract_start_date',
|
||||
'add_to_content_board' => 'nullable|boolean',
|
||||
'content_platform' => 'nullable|string|max:50',
|
||||
'content_type' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
$validated['is_priority'] = $request->boolean('is_priority');
|
||||
@@ -76,6 +89,7 @@ class EventController extends Controller
|
||||
|
||||
$event = Event::create(Arr::except($validated, [
|
||||
'create_brand_contract', 'contract_title', 'contract_start_date', 'contract_end_date',
|
||||
'add_to_content_board', 'content_platform', 'content_type',
|
||||
]));
|
||||
|
||||
if ($request->boolean('create_brand_contract') && $event->client_id) {
|
||||
@@ -89,6 +103,19 @@ class EventController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->boolean('add_to_content_board')) {
|
||||
ContentBrief::create([
|
||||
'title' => $event->title,
|
||||
'client_id' => $event->client_id,
|
||||
'category_id' => $event->category_id,
|
||||
'platform' => $request->input('content_platform', 'instagram'),
|
||||
'content_type' => $request->input('content_type', 'post'),
|
||||
'status' => ContentStatus::Drafting,
|
||||
'target_date' => $event->start_time->toDateString(),
|
||||
'notes' => $event->notes,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('schedule.index')
|
||||
->with('success', '🐾 Event scheduled!');
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InvoiceSettings;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -53,4 +54,35 @@ class InvoiceSettingsController extends Controller
|
||||
return redirect()->route('invoice-settings.edit')
|
||||
->with('success', '🧾 Invoice settings saved!');
|
||||
}
|
||||
|
||||
public function uploadLogo(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['header_logo' => 'required|image|max:2048']);
|
||||
|
||||
$settings = InvoiceSettings::instance();
|
||||
|
||||
if ($settings->header_logo) {
|
||||
Storage::disk('public')->delete($settings->header_logo);
|
||||
}
|
||||
|
||||
$path = $request->file('header_logo')->store('invoice-logos', 'public');
|
||||
$settings->update(['header_logo' => $path]);
|
||||
|
||||
return response()->json([
|
||||
'url' => asset('storage/' . $path),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteLogo(): RedirectResponse
|
||||
{
|
||||
$settings = InvoiceSettings::instance();
|
||||
|
||||
if ($settings->header_logo) {
|
||||
Storage::disk('public')->delete($settings->header_logo);
|
||||
$settings->update(['header_logo' => null]);
|
||||
}
|
||||
|
||||
return redirect()->route('invoice-settings.edit')
|
||||
->with('success', '🗑️ Logo removed.');
|
||||
}
|
||||
}
|
||||
|
||||
64
app/Http/Controllers/NoteController.php
Normal file
64
app/Http/Controllers/NoteController.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class NoteController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$notes = Note::orderByDesc('is_pinned')
|
||||
->orderByDesc('updated_at')
|
||||
->get();
|
||||
|
||||
return view('notes.index', compact('notes'));
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'nullable|array',
|
||||
'color' => 'nullable|string|max:20',
|
||||
'is_pinned' => 'boolean',
|
||||
]);
|
||||
|
||||
$note = Note::create($data);
|
||||
|
||||
return response()->json($note, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, Note $note): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'nullable|array',
|
||||
'color' => 'nullable|string|max:20',
|
||||
'is_pinned' => 'boolean',
|
||||
]);
|
||||
|
||||
$note->update($data);
|
||||
|
||||
return response()->json($note);
|
||||
}
|
||||
|
||||
public function destroy(Note $note): JsonResponse
|
||||
{
|
||||
$note->delete();
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function togglePin(Note $note): JsonResponse
|
||||
{
|
||||
$note->update(['is_pinned' => ! $note->is_pinned]);
|
||||
|
||||
return response()->json($note);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,11 @@ class MediaFile extends Model
|
||||
return str_starts_with($this->mime_type, 'image/');
|
||||
}
|
||||
|
||||
public function getIsVideoAttribute(): bool
|
||||
{
|
||||
return str_starts_with($this->mime_type, 'video/');
|
||||
}
|
||||
|
||||
public function getIconAttribute(): string
|
||||
{
|
||||
if ($this->is_image) return '🖼️';
|
||||
|
||||
22
app/Models/Note.php
Normal file
22
app/Models/Note.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Note extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'content',
|
||||
'color',
|
||||
'is_pinned',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'content' => 'array',
|
||||
'is_pinned' => 'boolean',
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user