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
|
/.idea
|
||||||
|
/.nova
|
||||||
|
/.vscode
|
||||||
|
/.zed
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@ class ForceJsonResponse
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
|
// Only force JSON for API routes
|
||||||
|
if ($request->is('api/*')) {
|
||||||
$request->headers->set('Accept', 'application/json');
|
$request->headers->set('Accept', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
return $next($request);
|
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",
|
"php": "^8.2",
|
||||||
"laravel/framework": "^12.9.2",
|
"laravel/framework": "^12.9.2",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/tinker": "^2.9"
|
"laravel/tinker": "^2.9",
|
||||||
|
"livewire/livewire": "^3.7"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"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:
|
services:
|
||||||
web:
|
laravel.test:
|
||||||
image: nginx:alpine
|
build:
|
||||||
container_name: dishplanner-nginx
|
context: './vendor/laravel/sail/runtimes/8.4'
|
||||||
restart: unless-stopped
|
dockerfile: Dockerfile
|
||||||
depends_on:
|
args:
|
||||||
- backend
|
WWWGROUP: '${WWWGROUP}'
|
||||||
volumes:
|
image: 'sail-8.4/app'
|
||||||
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
extra_hosts:
|
||||||
command: /bin/sh -c "until nslookup backend. ; do sleep 2; done && nginx -g 'daemon off;'"
|
- 'host.docker.internal:host-gateway'
|
||||||
ports:
|
ports:
|
||||||
- "3000:80"
|
- '${APP_PORT:-80}:80'
|
||||||
|
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
|
||||||
backend:
|
|
||||||
image: jochent/dishplanner-backend:v0.2
|
|
||||||
container_name: dishplanner-backend
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
environment:
|
||||||
DB_CONNECTION: mysql
|
WWWUSER: '${WWWUSER}'
|
||||||
DB_HOST: db
|
LARAVEL_SAIL: 1
|
||||||
DB_PORT: 3306
|
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
|
||||||
DB_DATABASE: dishplanner
|
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
|
||||||
DB_USERNAME: dishuser
|
IGNITION_LOCAL_SITES_PATH: '${PWD}'
|
||||||
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:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- '.:/var/www/html'
|
||||||
volumes:
|
networks:
|
||||||
db_data:
|
- 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:
|
networks:
|
||||||
default:
|
sail:
|
||||||
name: dishplanner-net
|
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