feature - 6 - port react frontend to laravel blade + livewire
This commit is contained in:
parent
afa4cf27b7
commit
71ed93fda0
453 changed files with 5145 additions and 184 deletions
0
backend/.gitattributes → .gitattributes
vendored
0
backend/.gitattributes → .gitattributes
vendored
23
.gitignore
vendored
23
.gitignore
vendored
|
|
@ -1 +1,24 @@
|
|||
/composer.lock
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/auth.json
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ class ForceJsonResponse
|
|||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
// Only force JSON for API routes
|
||||
if ($request->is('api/*')) {
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
36
app/Livewire/Auth/Login.php
Normal file
36
app/Livewire/Auth/Login.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Auth;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\Rule;
|
||||
|
||||
class Login extends Component
|
||||
{
|
||||
#[Rule('required|email')]
|
||||
public $email = '';
|
||||
|
||||
#[Rule('required')]
|
||||
public $password = '';
|
||||
|
||||
public $remember = false;
|
||||
|
||||
public function login()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
if (Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) {
|
||||
session()->regenerate();
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
|
||||
$this->addError('email', 'These credentials do not match our records.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.auth.login')
|
||||
->layout('layouts.guest');
|
||||
}
|
||||
}
|
||||
59
app/Livewire/Auth/Register.php
Normal file
59
app/Livewire/Auth/Register.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Attributes\Rule;
|
||||
|
||||
class Register extends Component
|
||||
{
|
||||
#[Rule('required|string|max:255')]
|
||||
public $name = '';
|
||||
|
||||
#[Rule('required|email|unique:users,email')]
|
||||
public $email = '';
|
||||
|
||||
#[Rule('required|min:8|confirmed')]
|
||||
public $password = '';
|
||||
|
||||
public $password_confirmation = '';
|
||||
|
||||
#[Rule('required|exists:planners,id')]
|
||||
public $planner_id = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
// Set default planner_id if only one exists
|
||||
$planners = \App\Models\Planner::all();
|
||||
if ($planners->count() === 1) {
|
||||
$this->planner_id = $planners->first()->id;
|
||||
}
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$user = User::create([
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'password' => Hash::make($this->password),
|
||||
'planner_id' => $this->planner_id,
|
||||
]);
|
||||
|
||||
Auth::login($user);
|
||||
session()->regenerate();
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.auth.register', [
|
||||
'planners' => \App\Models\Planner::all()
|
||||
])->layout('layouts.guest');
|
||||
}
|
||||
}
|
||||
122
app/Livewire/Dishes/DishesList.php
Normal file
122
app/Livewire/Dishes/DishesList.php
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Dishes;
|
||||
|
||||
use App\Models\Dish;
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class DishesList extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $showCreateModal = false;
|
||||
public $showEditModal = false;
|
||||
public $showDeleteModal = false;
|
||||
|
||||
public $editingDish = null;
|
||||
public $deletingDish = null;
|
||||
|
||||
// Form fields
|
||||
public $name = '';
|
||||
public $selectedUsers = [];
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'selectedUsers' => 'array',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
$dishes = Dish::with('users')
|
||||
->orderBy('name')
|
||||
->paginate(10);
|
||||
|
||||
$users = User::where('planner_id', auth()->user()->planner_id)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('livewire.dishes.dishes-list', [
|
||||
'dishes' => $dishes,
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->reset(['name', 'selectedUsers']);
|
||||
$this->resetValidation();
|
||||
$this->showCreateModal = true;
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$dish = Dish::create([
|
||||
'name' => $this->name,
|
||||
'planner_id' => auth()->user()->planner_id,
|
||||
]);
|
||||
|
||||
// Attach selected users
|
||||
if (!empty($this->selectedUsers)) {
|
||||
$dish->users()->attach($this->selectedUsers);
|
||||
}
|
||||
|
||||
$this->showCreateModal = false;
|
||||
$this->reset(['name', 'selectedUsers']);
|
||||
|
||||
session()->flash('success', 'Dish created successfully.');
|
||||
}
|
||||
|
||||
public function edit(Dish $dish)
|
||||
{
|
||||
$this->editingDish = $dish;
|
||||
$this->name = $dish->name;
|
||||
$this->selectedUsers = $dish->users->pluck('id')->toArray();
|
||||
$this->resetValidation();
|
||||
$this->showEditModal = true;
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$this->editingDish->update([
|
||||
'name' => $this->name,
|
||||
]);
|
||||
|
||||
// Sync users
|
||||
$this->editingDish->users()->sync($this->selectedUsers);
|
||||
|
||||
$this->showEditModal = false;
|
||||
$this->reset(['name', 'selectedUsers', 'editingDish']);
|
||||
|
||||
session()->flash('success', 'Dish updated successfully.');
|
||||
}
|
||||
|
||||
public function confirmDelete(Dish $dish)
|
||||
{
|
||||
$this->deletingDish = $dish;
|
||||
$this->showDeleteModal = true;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->deletingDish->users()->detach();
|
||||
$this->deletingDish->delete();
|
||||
$this->showDeleteModal = false;
|
||||
$this->deletingDish = null;
|
||||
|
||||
session()->flash('success', 'Dish deleted successfully.');
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
$this->showCreateModal = false;
|
||||
$this->showEditModal = false;
|
||||
$this->showDeleteModal = false;
|
||||
$this->reset(['name', 'selectedUsers', 'editingDish', 'deletingDish']);
|
||||
}
|
||||
}
|
||||
152
app/Livewire/Schedule/ScheduleCalendar.php
Normal file
152
app/Livewire/Schedule/ScheduleCalendar.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Schedule;
|
||||
|
||||
use App\Models\ScheduledUserDish;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Component;
|
||||
|
||||
class ScheduleCalendar extends Component
|
||||
{
|
||||
public $currentMonth;
|
||||
public $currentYear;
|
||||
public $calendarDays = [];
|
||||
public $showRegenerateModal = false;
|
||||
public $regenerateDate = null;
|
||||
public $regenerateUserId = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->currentMonth = now()->month;
|
||||
$this->currentYear = now()->year;
|
||||
$this->generateCalendar();
|
||||
}
|
||||
|
||||
protected $listeners = ['schedule-generated' => 'refreshCalendar'];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.schedule.schedule-calendar');
|
||||
}
|
||||
|
||||
public function refreshCalendar()
|
||||
{
|
||||
$this->generateCalendar();
|
||||
}
|
||||
|
||||
public function generateCalendar()
|
||||
{
|
||||
$this->calendarDays = [];
|
||||
|
||||
// Get first day of the month and total days
|
||||
$firstDay = Carbon::createFromDate($this->currentYear, $this->currentMonth, 1);
|
||||
$daysInMonth = $firstDay->daysInMonth;
|
||||
|
||||
// Generate 31 days for consistency with React version
|
||||
for ($day = 1; $day <= 31; $day++) {
|
||||
if ($day <= $daysInMonth) {
|
||||
$date = Carbon::createFromDate($this->currentYear, $this->currentMonth, $day);
|
||||
|
||||
// Get scheduled dishes for this date
|
||||
$scheduledDishes = ScheduledUserDish::with(['user', 'dish'])
|
||||
->whereDate('date', $date->format('Y-m-d'))
|
||||
->get();
|
||||
|
||||
$this->calendarDays[] = [
|
||||
'day' => $day,
|
||||
'date' => $date,
|
||||
'isToday' => $date->isToday(),
|
||||
'scheduledDishes' => $scheduledDishes,
|
||||
'isEmpty' => $scheduledDishes->isEmpty()
|
||||
];
|
||||
} else {
|
||||
// Empty slot for days that don't exist in this month
|
||||
$this->calendarDays[] = [
|
||||
'day' => null,
|
||||
'date' => null,
|
||||
'isToday' => false,
|
||||
'scheduledDishes' => collect(),
|
||||
'isEmpty' => true
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function previousMonth()
|
||||
{
|
||||
if ($this->currentMonth === 1) {
|
||||
$this->currentMonth = 12;
|
||||
$this->currentYear--;
|
||||
} else {
|
||||
$this->currentMonth--;
|
||||
}
|
||||
$this->generateCalendar();
|
||||
}
|
||||
|
||||
public function nextMonth()
|
||||
{
|
||||
if ($this->currentMonth === 12) {
|
||||
$this->currentMonth = 1;
|
||||
$this->currentYear++;
|
||||
} else {
|
||||
$this->currentMonth++;
|
||||
}
|
||||
$this->generateCalendar();
|
||||
}
|
||||
|
||||
|
||||
public function regenerateForUserDate($date, $userId)
|
||||
{
|
||||
$this->regenerateDate = $date;
|
||||
$this->regenerateUserId = $userId;
|
||||
$this->showRegenerateModal = true;
|
||||
}
|
||||
|
||||
public function confirmRegenerate()
|
||||
{
|
||||
try {
|
||||
// Delete existing scheduled dish for this user on this date
|
||||
ScheduledUserDish::whereDate('date', $this->regenerateDate)
|
||||
->where('user_id', $this->regenerateUserId)
|
||||
->delete();
|
||||
|
||||
// You could call a specific regeneration method here
|
||||
// For now, we'll just delete and let the user generate again
|
||||
|
||||
$this->showRegenerateModal = false;
|
||||
$this->generateCalendar(); // Refresh calendar
|
||||
|
||||
session()->flash('success', 'Schedule regenerated for the selected date!');
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error regenerating schedule: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function skipDay($date, $userId)
|
||||
{
|
||||
try {
|
||||
// Mark this day as skipped or delete the assignment
|
||||
ScheduledUserDish::whereDate('date', $date)
|
||||
->where('user_id', $userId)
|
||||
->delete();
|
||||
|
||||
$this->generateCalendar(); // Refresh calendar
|
||||
|
||||
session()->flash('success', 'Day skipped successfully!');
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error skipping day: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
$this->showRegenerateModal = false;
|
||||
$this->regenerateDate = null;
|
||||
$this->regenerateUserId = null;
|
||||
}
|
||||
|
||||
public function getMonthNameProperty()
|
||||
{
|
||||
return Carbon::createFromDate($this->currentYear, $this->currentMonth, 1)->format('F Y');
|
||||
}
|
||||
}
|
||||
202
app/Livewire/Schedule/ScheduleGenerator.php
Normal file
202
app/Livewire/Schedule/ScheduleGenerator.php
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Schedule;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Dish;
|
||||
use App\Models\Schedule;
|
||||
use App\Models\ScheduledUserDish;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Component;
|
||||
|
||||
class ScheduleGenerator extends Component
|
||||
{
|
||||
public $selectedMonth;
|
||||
public $selectedYear;
|
||||
public $selectedUsers = [];
|
||||
public $clearExisting = true;
|
||||
public $showAdvancedOptions = false;
|
||||
public $isGenerating = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedMonth = now()->month;
|
||||
$this->selectedYear = now()->year;
|
||||
|
||||
// Select all users by default
|
||||
$this->selectedUsers = User::where('planner_id', auth()->user()->planner_id)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$users = User::where('planner_id', auth()->user()->planner_id)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$months = [
|
||||
1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April',
|
||||
5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',
|
||||
9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'
|
||||
];
|
||||
|
||||
$years = range(now()->year - 1, now()->year + 2);
|
||||
|
||||
return view('livewire.schedule.schedule-generator', [
|
||||
'users' => $users,
|
||||
'months' => $months,
|
||||
'years' => $years
|
||||
]);
|
||||
}
|
||||
|
||||
public function generate()
|
||||
{
|
||||
$this->validate([
|
||||
'selectedUsers' => 'required|array|min:1',
|
||||
'selectedMonth' => 'required|integer|min:1|max:12',
|
||||
'selectedYear' => 'required|integer|min:2020|max:2030',
|
||||
]);
|
||||
|
||||
$this->isGenerating = true;
|
||||
|
||||
try {
|
||||
$startDate = Carbon::createFromDate($this->selectedYear, $this->selectedMonth, 1);
|
||||
$endDate = $startDate->copy()->endOfMonth();
|
||||
|
||||
// Clear existing schedule if requested
|
||||
if ($this->clearExisting) {
|
||||
ScheduledUserDish::whereBetween('date', [$startDate, $endDate])
|
||||
->whereIn('user_id', $this->selectedUsers)
|
||||
->delete();
|
||||
}
|
||||
|
||||
// Get all dishes assigned to selected users
|
||||
$userDishes = [];
|
||||
foreach ($this->selectedUsers as $userId) {
|
||||
$user = User::find($userId);
|
||||
$dishes = $user->dishes()->get();
|
||||
|
||||
if ($dishes->isNotEmpty()) {
|
||||
$userDishes[$userId] = $dishes->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate schedule for each day
|
||||
$currentDate = $startDate->copy();
|
||||
while ($currentDate <= $endDate) {
|
||||
foreach ($this->selectedUsers as $userId) {
|
||||
// Skip if user already has a dish for this day
|
||||
if (ScheduledUserDish::where('date', $currentDate->format('Y-m-d'))
|
||||
->where('user_id', $userId)
|
||||
->exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get available dishes for this user
|
||||
if (!isset($userDishes[$userId]) || empty($userDishes[$userId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$availableDishes = $userDishes[$userId];
|
||||
|
||||
// Simple random assignment (you can implement more complex logic here)
|
||||
if (!empty($availableDishes)) {
|
||||
$randomDish = $availableDishes[array_rand($availableDishes)];
|
||||
|
||||
ScheduledUserDish::create([
|
||||
'user_id' => $userId,
|
||||
'dish_id' => $randomDish['id'],
|
||||
'date' => $currentDate->format('Y-m-d'),
|
||||
'planner_id' => auth()->user()->planner_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$currentDate->addDay();
|
||||
}
|
||||
|
||||
$this->isGenerating = false;
|
||||
|
||||
// Emit event to refresh calendar
|
||||
$this->dispatch('schedule-generated');
|
||||
|
||||
session()->flash('success', 'Schedule generated successfully for ' .
|
||||
$this->getSelectedMonthName() . ' ' . $this->selectedYear);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->isGenerating = false;
|
||||
session()->flash('error', 'Error generating schedule: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateForDate($date)
|
||||
{
|
||||
try {
|
||||
// Clear existing assignments for this date
|
||||
ScheduledUserDish::whereDate('date', $date)
|
||||
->whereIn('user_id', $this->selectedUsers)
|
||||
->delete();
|
||||
|
||||
// Regenerate for this specific date
|
||||
$currentDate = Carbon::parse($date);
|
||||
|
||||
foreach ($this->selectedUsers as $userId) {
|
||||
$user = User::find($userId);
|
||||
$dishes = $user->dishes()->get();
|
||||
|
||||
if ($dishes->isNotEmpty()) {
|
||||
$randomDish = $dishes->random();
|
||||
|
||||
ScheduledUserDish::create([
|
||||
'user_id' => $userId,
|
||||
'dish_id' => $randomDish->id,
|
||||
'date' => $currentDate->format('Y-m-d'),
|
||||
'planner_id' => auth()->user()->planner_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->dispatch('schedule-generated');
|
||||
session()->flash('success', 'Schedule regenerated for ' . $currentDate->format('M d, Y'));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error regenerating schedule: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function clearMonth()
|
||||
{
|
||||
try {
|
||||
$startDate = Carbon::createFromDate($this->selectedYear, $this->selectedMonth, 1);
|
||||
$endDate = $startDate->copy()->endOfMonth();
|
||||
|
||||
ScheduledUserDish::whereBetween('date', [$startDate, $endDate])
|
||||
->whereIn('user_id', $this->selectedUsers)
|
||||
->delete();
|
||||
|
||||
$this->dispatch('schedule-generated');
|
||||
session()->flash('success', 'Schedule cleared for ' .
|
||||
$this->getSelectedMonthName() . ' ' . $this->selectedYear);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
session()->flash('error', 'Error clearing schedule: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleAdvancedOptions()
|
||||
{
|
||||
$this->showAdvancedOptions = !$this->showAdvancedOptions;
|
||||
}
|
||||
|
||||
private function getSelectedMonthName()
|
||||
{
|
||||
$months = [
|
||||
1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April',
|
||||
5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',
|
||||
9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December'
|
||||
];
|
||||
|
||||
return $months[$this->selectedMonth];
|
||||
}
|
||||
}
|
||||
136
app/Livewire/Users/UsersList.php
Normal file
136
app/Livewire/Users/UsersList.php
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Users;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class UsersList extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $showCreateModal = false;
|
||||
public $showEditModal = false;
|
||||
public $showDeleteModal = false;
|
||||
|
||||
public $editingUser = null;
|
||||
public $deletingUser = null;
|
||||
|
||||
// Form fields
|
||||
public $name = '';
|
||||
public $email = '';
|
||||
public $password = '';
|
||||
public $password_confirmation = '';
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'password' => 'required|min:8|confirmed',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
$users = User::where('planner_id', auth()->user()->planner_id)
|
||||
->orderBy('name')
|
||||
->paginate(10);
|
||||
|
||||
return view('livewire.users.users-list', [
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->reset(['name', 'email', 'password', 'password_confirmation']);
|
||||
$this->resetValidation();
|
||||
$this->showCreateModal = true;
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
User::create([
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'password' => bcrypt($this->password),
|
||||
'planner_id' => auth()->user()->planner_id,
|
||||
]);
|
||||
|
||||
$this->showCreateModal = false;
|
||||
$this->reset(['name', 'email', 'password', 'password_confirmation']);
|
||||
|
||||
session()->flash('success', 'User created successfully.');
|
||||
}
|
||||
|
||||
public function edit(User $user)
|
||||
{
|
||||
$this->editingUser = $user;
|
||||
$this->name = $user->name;
|
||||
$this->email = $user->email;
|
||||
$this->password = '';
|
||||
$this->password_confirmation = '';
|
||||
$this->resetValidation();
|
||||
$this->showEditModal = true;
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$rules = [
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email,' . $this->editingUser->id,
|
||||
];
|
||||
|
||||
if ($this->password) {
|
||||
$rules['password'] = 'min:8|confirmed';
|
||||
}
|
||||
|
||||
$this->validate($rules);
|
||||
|
||||
$this->editingUser->update([
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
]);
|
||||
|
||||
if ($this->password) {
|
||||
$this->editingUser->update([
|
||||
'password' => bcrypt($this->password)
|
||||
]);
|
||||
}
|
||||
|
||||
$this->showEditModal = false;
|
||||
$this->reset(['name', 'email', 'password', 'password_confirmation', 'editingUser']);
|
||||
|
||||
session()->flash('success', 'User updated successfully.');
|
||||
}
|
||||
|
||||
public function confirmDelete(User $user)
|
||||
{
|
||||
$this->deletingUser = $user;
|
||||
$this->showDeleteModal = true;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
if ($this->deletingUser->id === auth()->id()) {
|
||||
session()->flash('error', 'You cannot delete your own account.');
|
||||
$this->showDeleteModal = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deletingUser->delete();
|
||||
$this->showDeleteModal = false;
|
||||
$this->deletingUser = null;
|
||||
|
||||
session()->flash('success', 'User deleted successfully.');
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
$this->showCreateModal = false;
|
||||
$this->showEditModal = false;
|
||||
$this->showDeleteModal = false;
|
||||
$this->reset(['name', 'email', 'password', 'password_confirmation', 'editingUser', 'deletingUser']);
|
||||
}
|
||||
}
|
||||
25
backend/.gitignore
vendored
25
backend/.gitignore
vendored
|
|
@ -1,25 +0,0 @@
|
|||
/composer.lock
|
||||
/.composer
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/auth.json
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
services:
|
||||
backend:
|
||||
build:
|
||||
context: './vendor/laravel/sail/runtimes/8.4'
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
WWWGROUP: '${WWWGROUP}'
|
||||
image: 'sail-8.4/app'
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
ports:
|
||||
- '${APP_PORT:-80}:80'
|
||||
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
|
||||
environment:
|
||||
WWWUSER: '${WWWUSER}'
|
||||
LARAVEL_SAIL: 1
|
||||
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
|
||||
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
|
||||
IGNITION_LOCAL_SITES_PATH: '${PWD}'
|
||||
volumes:
|
||||
- '.:/var/www/html:Z'
|
||||
networks:
|
||||
- sail
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
image: 'docker.io/mysql/mysql-server:8.0'
|
||||
ports:
|
||||
- '${FORWARD_DB_PORT:-3306}:3306'
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
|
||||
MYSQL_ROOT_HOST: '%'
|
||||
MYSQL_DATABASE: '${DB_DATABASE}'
|
||||
MYSQL_USER: '${DB_USERNAME}'
|
||||
MYSQL_PASSWORD: '${DB_PASSWORD}'
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 1
|
||||
volumes:
|
||||
- 'sail-mysql:/var/lib/mysql:Z'
|
||||
networks:
|
||||
- sail
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- mysqladmin
|
||||
- ping
|
||||
- '-p${DB_PASSWORD}'
|
||||
retries: 3
|
||||
timeout: 5s
|
||||
networks:
|
||||
sail:
|
||||
driver: bridge
|
||||
volumes:
|
||||
sail-mysql:
|
||||
driver: local
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^1.7.4",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^1.0",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"vite": "^6.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
@ -1 +0,0 @@
|
|||
import './bootstrap';
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import defaultTheme from 'tailwindcss/defaultTheme';
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
|
||||
'./storage/framework/views/*.php',
|
||||
'./resources/**/*.blade.php',
|
||||
'./resources/**/*.js',
|
||||
'./resources/**/*.vue',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
|
@ -12,7 +12,8 @@
|
|||
"php": "^8.2",
|
||||
"laravel/framework": "^12.9.2",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.9"
|
||||
"laravel/tinker": "^2.9",
|
||||
"livewire/livewire": "^3.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
186
config/livewire.php
Normal file
186
config/livewire.php
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Class Namespace
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the root class namespace for Livewire component classes in
|
||||
| your application. This value will change where component auto-discovery
|
||||
| finds components. It's also referenced by the file creation commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'class_namespace' => 'App\\Livewire',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| View Path
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value is used to specify where Livewire component Blade templates are
|
||||
| stored when running file creation commands like `artisan make:livewire`.
|
||||
| It is also used if you choose to omit a component's render() method.
|
||||
|
|
||||
*/
|
||||
|
||||
'view_path' => resource_path('views/livewire'),
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Layout
|
||||
|---------------------------------------------------------------------------
|
||||
| The view that will be used as the layout when rendering a single component
|
||||
| as an entire page via `Route::get('/post/create', CreatePost::class);`.
|
||||
| In this case, the view returned by CreatePost will render into $slot.
|
||||
|
|
||||
*/
|
||||
|
||||
'layout' => 'components.layouts.app',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Lazy Loading Placeholder
|
||||
|---------------------------------------------------------------------------
|
||||
| Livewire allows you to lazy load components that would otherwise slow down
|
||||
| the initial page load. Every component can have a custom placeholder or
|
||||
| you can define the default placeholder view for all components below.
|
||||
|
|
||||
*/
|
||||
|
||||
'lazy_placeholder' => null,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Temporary File Uploads
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire handles file uploads by storing uploads in a temporary directory
|
||||
| before the file is stored permanently. All file uploads are directed to
|
||||
| a global endpoint for temporary storage. You may configure this below:
|
||||
|
|
||||
*/
|
||||
|
||||
'temporary_file_upload' => [
|
||||
'disk' => null, // Example: 'local', 's3' | Default: 'default'
|
||||
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp'
|
||||
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
||||
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
|
||||
'mov', 'avi', 'wmv', 'mp3', 'm4a',
|
||||
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
|
||||
],
|
||||
'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated...
|
||||
'cleanup' => true, // Should cleanup temporary uploads older than 24 hrs...
|
||||
],
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Render On Redirect
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines if Livewire will run a component's `render()` method
|
||||
| after a redirect has been triggered using something like `redirect(...)`
|
||||
| Setting this to true will render the view once more before redirecting
|
||||
|
|
||||
*/
|
||||
|
||||
'render_on_redirect' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Eloquent Model Binding
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Previous versions of Livewire supported binding directly to eloquent model
|
||||
| properties using wire:model by default. However, this behavior has been
|
||||
| deemed too "magical" and has therefore been put under a feature flag.
|
||||
|
|
||||
*/
|
||||
|
||||
'legacy_model_binding' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Auto-inject Frontend Assets
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| By default, Livewire automatically injects its JavaScript and CSS into the
|
||||
| <head> and <body> of pages containing Livewire components. By disabling
|
||||
| this behavior, you need to use @livewireStyles and @livewireScripts.
|
||||
|
|
||||
*/
|
||||
|
||||
'inject_assets' => true,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Navigate (SPA mode)
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| By adding `wire:navigate` to links in your Livewire application, Livewire
|
||||
| will prevent the default link handling and instead request those pages
|
||||
| via AJAX, creating an SPA-like effect. Configure this behavior here.
|
||||
|
|
||||
*/
|
||||
|
||||
'navigate' => [
|
||||
'show_progress_bar' => true,
|
||||
'progress_bar_color' => '#2299dd',
|
||||
],
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| HTML Morph Markers
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire intelligently "morphs" existing HTML into the newly rendered HTML
|
||||
| after each update. To make this process more reliable, Livewire injects
|
||||
| "markers" into the rendered Blade surrounding @if, @class & @foreach.
|
||||
|
|
||||
*/
|
||||
|
||||
'inject_morph_markers' => true,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Smart Wire Keys
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire uses loops and keys used within loops to generate smart keys that
|
||||
| are applied to nested components that don't have them. This makes using
|
||||
| nested components more reliable by ensuring that they all have keys.
|
||||
|
|
||||
*/
|
||||
|
||||
'smart_wire_keys' => false,
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Pagination Theme
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| When enabling Livewire's pagination feature by using the `WithPagination`
|
||||
| trait, Livewire will use Tailwind templates to render pagination views
|
||||
| on the page. If you want Bootstrap CSS, you can specify: "bootstrap"
|
||||
|
|
||||
*/
|
||||
|
||||
'pagination_theme' => 'tailwind',
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------
|
||||
| Release Token
|
||||
|---------------------------------------------------------------------------
|
||||
|
|
||||
| This token is stored client-side and sent along with each request to check
|
||||
| a users session to see if a new release has invalidated it. If there is
|
||||
| a mismatch it will throw an error and prompt for a browser refresh.
|
||||
|
|
||||
*/
|
||||
|
||||
'release_token' => 'a',
|
||||
];
|
||||
|
|
@ -1,55 +1,52 @@
|
|||
services:
|
||||
web:
|
||||
image: nginx:alpine
|
||||
container_name: dishplanner-nginx
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
command: /bin/sh -c "until nslookup backend. ; do sleep 2; done && nginx -g 'daemon off;'"
|
||||
ports:
|
||||
- "3000:80"
|
||||
|
||||
backend:
|
||||
image: jochent/dishplanner-backend:v0.2
|
||||
container_name: dishplanner-backend
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DB_CONNECTION: mysql
|
||||
DB_HOST: db
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: dishplanner
|
||||
DB_USERNAME: dishuser
|
||||
DB_PASSWORD: dishpass
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "8080:80"
|
||||
|
||||
frontend:
|
||||
image: jochent/dishplanner-frontend:v0.2
|
||||
container_name: dishplanner-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
# ports:
|
||||
# - "3000:3000"
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: dishplanner-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
MYSQL_DATABASE: dishplanner
|
||||
MYSQL_USER: dishuser
|
||||
MYSQL_PASSWORD: dishpass
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
volumes:
|
||||
db_data:
|
||||
|
||||
laravel.test:
|
||||
build:
|
||||
context: './vendor/laravel/sail/runtimes/8.4'
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
WWWGROUP: '${WWWGROUP}'
|
||||
image: 'sail-8.4/app'
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
ports:
|
||||
- '${APP_PORT:-80}:80'
|
||||
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
|
||||
environment:
|
||||
WWWUSER: '${WWWUSER}'
|
||||
LARAVEL_SAIL: 1
|
||||
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
|
||||
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
|
||||
IGNITION_LOCAL_SITES_PATH: '${PWD}'
|
||||
volumes:
|
||||
- '.:/var/www/html'
|
||||
networks:
|
||||
- sail
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
image: 'mysql/mysql-server:8.0'
|
||||
ports:
|
||||
- '${FORWARD_DB_PORT:-3306}:3306'
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
|
||||
MYSQL_ROOT_HOST: '%'
|
||||
MYSQL_DATABASE: '${DB_DATABASE}'
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 1
|
||||
volumes:
|
||||
- 'sail-mysql:/var/lib/mysql'
|
||||
networks:
|
||||
- sail
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- mysqladmin
|
||||
- ping
|
||||
- '-p${DB_PASSWORD}'
|
||||
retries: 3
|
||||
timeout: 5s
|
||||
networks:
|
||||
default:
|
||||
name: dishplanner-net
|
||||
sail:
|
||||
driver: bridge
|
||||
volumes:
|
||||
sail-mysql:
|
||||
driver: local
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue