codes convertion using livewire

This commit is contained in:
armiejean 2025-04-10 17:21:37 +08:00
parent 9456bb3d57
commit 0077ae9ddb
114 changed files with 5477 additions and 281 deletions

View File

@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class AdvanceSearchFilter extends Component
{
public $filterField = '1'; // Default value
public $filterValueStart = '';
public $filterValueEnd = '';
public function submit()
{
$this->dispatch('formSubmitted', [
'filter_field' => $this->filterField,
'filter_value_start' => $this->filterValueStart,
'filter_value_end' => $this->filterValueEnd,
]);
}
public function resetForm()
{
$this->reset(['filterField', 'filterValueStart', 'filterValueEnd']);
$this->filterField = '1'; // Reset to default
}
public function render()
{
return view('livewire.advance-search-filter');
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class CascaderForm extends Component
{
public $options = [];
public $selected = [];
public $url;
public function mount($url)
{
$this->url = $url;
$this->fetchOptions();
}
public function fetchOptions()
{
try {
$response = Http::get($this->url);
$this->options = $response->json('data') ?? [
['value' => 'zhejiang', 'label' => 'Zhejiang', 'children' => [
['value' => 'hangzhou', 'label' => 'Hangzhou', 'children' => [
['value' => 'xihu', 'label' => 'West Lake']
]]
]],
['value' => 'jiangsu', 'label' => 'Jiangsu', 'children' => [
['value' => 'nanjing', 'label' => 'Nanjing', 'children' => [
['value' => 'zhonghuamen', 'label' => 'Zhong Hua Men']
]]
]]
];
} catch (\Exception $e) {
session()->flash('error', 'Failed to load options: ' . $e->getMessage());
}
}
public function updatedSelected()
{
$this->dispatch('cascaderChanged', $this->selected);
}
public function render()
{
return view('livewire.cascader-form');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class CheckboxForm extends Component
{
public $value = false;
public $name;
public $label;
public $inline = false;
public $required = false;
public function mount($name, $label, $inline = false, $required = false)
{
$this->name = $name;
$this->label = $label;
$this->inline = $inline;
$this->required = $required;
}
public function updatedValue()
{
$this->dispatch('checkboxChanged', $this->value);
}
public function render()
{
return view('livewire.checkbox-form');
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class CustomTable extends Component
{
public $data = [];
public $loading = false;
public $currentPage = 1;
public $pageSize = 10;
public $totalData = 0;
public $searchValue = '';
public $sortedInfo = null;
public $filteredInfo = [];
public $columns = [];
public $actions = [];
public $url;
public $keyValue = 'id';
// Initialize the component with URL, columns, actions, and key
public function mount($url, $columns, $actions = [], $keyValue = 'id')
{
$this->url = $url;
$this->columns = $columns;
$this->actions = $actions;
$this->keyValue = $keyValue;
$this->fetchData();
}
// Update data when search value changes (debounced in Blade)
public function updatedSearchValue()
{
$this->currentPage = 1; // Reset to first page on search
$this->fetchData();
}
// Toggle sorting for a column
public function sort($field)
{
if ($this->sortedInfo && $this->sortedInfo['field'] === $field) {
$this->sortedInfo['order'] = $this->sortedInfo['order'] === 'asc' ? 'desc' : 'asc';
} else {
$this->sortedInfo = ['field' => $field, 'order' => 'asc'];
}
$this->fetchData();
}
// Fetch data from the API
public function fetchData()
{
$this->loading = true;
$params = [
'_page' => $this->currentPage,
'_limit' => $this->pageSize,
'q' => $this->searchValue,
'_sort' => $this->sortedInfo ? $this->sortedInfo['field'] : null,
'_order' => $this->sortedInfo ? $this->sortedInfo['order'] : null,
];
try {
$response = Http::get($this->url, $params);
$this->data = $response->json('data') ?? []; // Adjust based on API response structure
$this->totalData = $response->json('total') ?? count($this->data);
} catch (\Exception $e) {
session()->flash('error', 'Failed to load data: ' . $e->getMessage());
} finally {
$this->loading = false;
}
}
// Handle pagination
public function handlePagination($page)
{
$this->currentPage = $page;
$this->fetchData();
}
// Handle page size change (optional extension)
public function handleSizeChange($size)
{
$this->pageSize = $size;
$this->currentPage = 1; // Reset to first page
$this->fetchData();
}
// Clear all filters, sorting, and search
public function handleClearAll()
{
$this->searchValue = '';
$this->sortedInfo = null;
$this->filteredInfo = [];
$this->currentPage = 1;
$this->fetchData();
}
// Render the Blade view
public function render()
{
return view('livewire.custom-table');
}
}

41
app/Livewire/DataList.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class DataList extends Component
{
public $data = [];
public $url;
public $avatar = false;
public $viewPath;
public $header;
public $footer;
public function mount($url, $avatar = false, $viewPath, $header = null, $footer = null)
{
$this->url = $url;
$this->avatar = $avatar;
$this->viewPath = $viewPath;
$this->header = $header;
$this->footer = $footer;
$this->fetchData();
}
public function fetchData()
{
try {
$response = Http::get($this->url);
$this->data = $response->json('data') ?? [];
} catch (\Exception $e) {
session()->flash('error', 'Failed to load data: ' . $e->getMessage());
}
}
public function render()
{
return view('livewire.data-list');
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Carbon\Carbon;
class DatePickerForm extends Component
{
public $value;
public $type = 'date';
public $name;
public $label;
public $format = 'Y-m-d';
public $minDateToday = false;
public $required = false;
public $disabledDateStart = false;
public $dateStartEnd = null;
public $disabledDateStartEndPhotoSlider = false;
public $disabledDateStartEndPhotoSliderEndDate = false;
public $isEdit = false;
public function mount($name, $label, $type = 'date', $format = 'Y-m-d', $minDateToday = false, $required = false, $disabledDateStart = false, $dateStartEnd = null, $disabledDateStartEndPhotoSlider = false, $disabledDateStartEndPhotoSliderEndDate = false, $isEdit = false)
{
$this->name = $name;
$this->label = $label;
$this->type = $type;
$this->format = $format;
$this->minDateToday = $minDateToday;
$this->required = $required;
$this->disabledDateStart = $disabledDateStart;
$this->dateStartEnd = $dateStartEnd;
$this->disabledDateStartEndPhotoSlider = $disabledDateStartEndPhotoSlider;
$this->disabledDateStartEndPhotoSliderEndDate = $disabledDateStartEndPhotoSliderEndDate;
$this->isEdit = $isEdit;
}
public function updatedValue()
{
$this->dispatch('dateChanged', $this->value);
}
public function render()
{
$minDate = $this->minDateToday ? Carbon::today()->toDateString() : null;
if ($this->disabledDateStart && $this->dateStartEnd) {
$minDate = Carbon::parse($this->dateStartEnd['date_start'])->toDateString();
}
return view('livewire.date-picker-form', ['minDate' => $minDate]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class DropdownExport extends Component
{
public $loading = false;
public $url;
public $defaultFilter = [];
public function mount($url, $defaultFilter = [])
{
$this->url = $url;
$this->defaultFilter = $defaultFilter;
}
public function handleExportCSV()
{
$this->loading = true;
try {
// Merge query parameters with default filters
$params = array_merge(request()->query(), $this->defaultFilter);
$response = Http::withOptions(['response_type' => 'blob'])->get($this->url['path'], $params);
if ($response->successful()) {
$dateNow = now()->format('mdY');
$fileName = "{$this->url['fileName']}_{$dateNow}.csv";
Storage::disk('local')->put($fileName, $response->body());
return response()->download(storage_path("app/{$fileName}"))->deleteFileAfterSend(true);
} else {
session()->flash('error', 'Failed to export CSV.');
}
} catch (\Exception $e) {
session()->flash('error', 'An error occurred: ' . $e->getMessage());
} finally {
$this->loading = false;
}
}
public function render()
{
return view('livewire.dropdown-export');
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class HeaderForm extends Component
{
public $title;
public $actionBtnName = 'Save';
public $cancelBtnName = 'Cancel';
public $deleteBtnName = 'Delete';
public $loading = false;
public $withConfirm = false;
public $withCancelConfirm = false;
public $disabled = false;
public $isDropDown = false;
public $isInsideForm = false;
public function action()
{
$this->dispatch('actionTriggered');
}
public function cancel()
{
$this->dispatch('cancelTriggered');
}
public function deleteAction()
{
$this->dispatch('deleteTriggered');
}
public function actionPrivacy()
{
$this->dispatch('privacyTriggered');
}
public function actionTerms()
{
$this->dispatch('termsTriggered');
}
public function render()
{
return view('livewire.header-form');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class InputForm extends Component
{
public $value = '';
public $name;
public $label;
public $required = false;
public $withActionBtn = false;
public $isCopyUsername = false;
public $loading = false;
public function mount($name, $label, $required = false, $withActionBtn = false, $isCopyUsername = false, $loading = false)
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->withActionBtn = $withActionBtn;
$this->isCopyUsername = $isCopyUsername;
$this->loading = $loading;
}
public function updatedValue()
{
$this->dispatch('inputChanged', $this->value);
}
public function render()
{
return view('livewire.input-form');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class InputMaskNumberForm extends Component
{
public $value = '';
public $name;
public $label;
public $required = false;
public $mask = '999-999-9999'; // Example mask
public function mount($name, $label, $required = false, $mask = '999-999-9999')
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->mask = $mask;
}
public function updatedValue()
{
$this->dispatch('inputChanged', $this->value);
}
public function render()
{
return view('livewire.input-mask-number-form');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class InputNumberAntD extends Component
{
public $value = 0;
public $name;
public $label;
public $required = false;
public $min = null;
public $max = null;
public $step = 1;
public function mount($name, $label, $required = false, $min = null, $max = null, $step = 1)
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->min = $min;
$this->max = $max;
$this->step = $step;
}
public function updatedValue()
{
$this->dispatch('numberChanged', $this->value);
}
public function render()
{
return view('livewire.input-number-ant-d');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class InputPassword extends Component
{
public $value = '';
public $name;
public $label;
public $required = false;
public function mount($name, $label, $required = false)
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
}
public function updatedValue()
{
$this->dispatch('passwordChanged', $this->value);
}
public function render()
{
return view('livewire.input-password');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class InputTextArea extends Component
{
public $value = '';
public $name;
public $label;
public $required = false;
public $onCountText = false;
public $charsperpage = 100;
public $hasIcon = false;
public function mount($name, $label, $required = false, $onCountText = false, $charsperpage = 100, $hasIcon = false)
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->onCountText = $onCountText;
$this->charsperpage = $charsperpage;
$this->hasIcon = $hasIcon;
}
public function updatedValue()
{
$this->dispatch('textAreaChanged', $this->value);
}
public function render()
{
return view('livewire.input-text-area');
}
}

13
app/Livewire/Loading.php Normal file
View File

@ -0,0 +1,13 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class Loading extends Component
{
public function render()
{
return view('livewire.loading');
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class LoginLayout extends Component
{
public $children;
public function mount($children = null)
{
$this->children = $children;
}
public function render()
{
return view('livewire.login-layout');
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class ModalCancel extends Component
{
public $visible = false;
public $path;
public $title;
public $message;
public $id;
public $dirty = false;
public $name;
public $loading = false;
public function mount($path, $title = null, $message = null, $id = null, $dirty = false, $name = null, $loading = false)
{
$this->path = $path;
$this->title = $title ?? 'Your Title';
$this->message = $message ?? 'Your Message';
$this->id = $id;
$this->dirty = $dirty;
$this->name = $name ?? 'Cancel';
$this->loading = $loading;
}
public function showModal()
{
$this->visible = true;
}
public function handleCancel()
{
$this->visible = false;
}
public function render()
{
return view('livewire.modal-cancel');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class MultiSelectForm extends Component
{
public $value = [];
public $name;
public $label;
public $required = false;
public $optionsList = [];
public $url;
public function mount($name, $label, $required = false, $url = null, $optionsList = [])
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->url = $url;
$this->optionsList = $optionsList;
if ($url) {
$this->fetchOptions();
}
}
public function fetchOptions()
{
try {
$response = Http::get($this->url);
$this->optionsList = $response->json('data') ?? [];
} catch (\Exception $e) {
session()->flash('error', 'Failed to load options: ' . $e->getMessage());
}
}
public function updatedValue()
{
$this->dispatch('multiSelectChanged', $this->value);
}
public function render()
{
return view('livewire.multi-select-form');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class MultiSelectOptions extends Component
{
public $value = [];
public $name;
public $label;
public $required = false;
public $optionsList = [];
public function mount($name, $label, $required = false, $optionsList = [])
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->optionsList = $optionsList;
}
public function updatedValue()
{
$this->dispatch('multiSelectOptionsChanged', $this->value);
}
public function render()
{
return view('livewire.multi-select-options');
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class NotificationTable extends Component
{
public $data = [];
public $total = 0;
public $loading = false;
public $selectedRowKeys = [];
public $searchFilter = '';
public $mounted = false;
public $updating = false;
public $columns = [];
public $currentPage = 1;
public $perPage = 10;
public $sortBy = '';
public $sortOrder = '';
public $filters = [];
public $props = [];
protected $queryString = ['searchFilter', 'currentPage', 'perPage', 'sortBy', 'sortOrder', 'filters'];
public function mount($props)
{
$this->props = $props;
$this->mounted = true;
$this->columns = $props['columns'] ?? [];
$this->fetchData();
}
public function updatedSearchFilter()
{
$this->resetPage();
$this->fetchData();
}
public function setSort($field)
{
if ($this->sortBy === $field) {
$this->sortOrder = $this->sortOrder === 'asc' ? 'desc' : 'asc';
} else {
$this->sortBy = $field;
$this->sortOrder = 'asc';
}
$this->fetchData();
}
public function clearAll()
{
$this->reset(['searchFilter', 'filters', 'sortBy', 'sortOrder', 'selectedRowKeys']);
$this->fetchData();
}
public function updatePage($page)
{
$this->currentPage = $page;
$this->fetchData();
}
public function delete($uuid)
{
try {
$api = env('REACT_APP_STATION_API'); // Ensure this is set in your .env file
$path = substr(request()->path(), 1); // Get current path
$response = Http::delete("{$api}{$path}/{$uuid}");
if ($response->successful()) {
session()->flash('success', 'Record was successfully deleted.');
$this->fetchData();
} else {
session()->flash('error', 'Something went wrong deleting the record.');
}
} catch (\Exception $e) {
session()->flash('error', 'An error occurred: ' . $e->getMessage());
}
}
public function handleBatchDelete()
{
if (empty($this->selectedRowKeys)) return;
try {
$response = Http::delete("your-external-api-endpoint/{$this->props['url']['apiDelete']}", [
'data' => [$this->props['keyValue'] => $this->selectedRowKeys]
]);
if ($response->successful()) {
session()->flash('success', 'Records were successfully deleted.');
$this->selectedRowKeys = [];
$this->fetchData();
} else {
session()->flash('error', 'Something went wrong deleting records.');
}
} catch (\Exception $e) {
session()->flash('error', 'An error occurred: ' . $e->getMessage());
}
}
public function fetchData()
{
$this->loading = true;
$params = array_filter([
'page' => $this->currentPage,
'page_size' => $this->perPage,
'search' => $this->searchFilter,
'sort_by' => $this->sortBy,
'sort_order' => $this->sortOrder,
...$this->filters,
]);
try {
$defaultUrl = $this->props['url']['default'] ?? 'notification';
$response = Http::get("your-external-api-endpoint/{$defaultUrl}", $params);
$data = $response->json();
$this->data = $data['data'] ?? [];
$this->total = $data['total'] ?? 0;
if (empty($this->data) && $this->props['isEmptyMessagePopUp']) {
session()->flash('info', 'No records found.');
}
} catch (\Exception $e) {
session()->flash('error', 'An error occurred while fetching data: ' . $e->getMessage());
$this->data = [];
$this->total = 0;
} finally {
$this->loading = false;
}
}
public function render()
{
return view('livewire.notification-table', [
'columns' => $this->columns,
'keyValue' => $this->props['keyValue'] ?? 'id',
'apiDelete' => $this->props['url']['apiDelete'] ?? false,
'url' => $this->props['url'] ?? [],
]);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class Page404New extends Component
{
public function render()
{
return view('livewire.page404-new');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class RadioForm extends Component
{
public $value;
public $name;
public $label;
public $optionsList = [];
public $isRadioButton = false;
public $required = false;
public function mount($name, $label, $optionsList = [], $isRadioButton = false, $required = false)
{
$this->name = $name;
$this->label = $label;
$this->optionsList = $optionsList;
$this->isRadioButton = $isRadioButton;
$this->required = $required;
}
public function updatedValue()
{
$this->dispatch('radioChanged', $this->value);
}
public function render()
{
return view('livewire.radio-form');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Support\Facades\Http;
class SelectForm extends Component
{
public $value;
public $name;
public $label;
public $required = false;
public $optionsList = [];
public $url;
public function mount($name, $label, $required = false, $url = null, $optionsList = [])
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->url = $url;
$this->optionsList = $optionsList;
if ($url) {
$this->fetchOptions();
}
}
public function fetchOptions()
{
try {
$response = Http::get($this->url);
$this->optionsList = $response->json('data') ?? [];
} catch (\Exception $e) {
session()->flash('error', 'Failed to load options: ' . $e->getMessage());
}
}
public function updatedValue()
{
$this->dispatch('selectChanged', $this->value);
}
public function render()
{
return view('livewire.select-form');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
class SingleUploadImage extends Component
{
use WithFileUploads;
public $image;
public $name;
public $label;
public $required = false;
public $limit100kb = false;
public $imgStyle = ['width' => '100%', 'height' => '135px'];
public function updatedImage()
{
$this->validate([
'image' => 'image|mimes:jpeg,png,gif|max:' . ($this->limit100kb ? '100' : '2048'), // 100KB or 2MB
]);
$this->dispatch('imageUploaded', $this->image);
}
public function render()
{
return view('livewire.single-upload-image');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class TimePickerForm extends Component
{
public $value;
public $name;
public $label;
public $format = 'H:i';
public $required = false;
public function mount($name, $label, $format = 'H:i', $required = false)
{
$this->name = $name;
$this->label = $label;
$this->format = $format;
$this->required = $required;
}
public function updatedValue()
{
$this->dispatch('timeChanged', $this->value);
}
public function render()
{
return view('livewire.time-picker-form');
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
class UploadImage extends Component
{
use WithFileUploads;
public $image; // Holds the uploaded file(s)
public $name; // Input name
public $label; // Input label
public $required = false; // Required field flag
public $limit100kb = false; // Limit file size to 100KB or 2MB
public $imgStyle = ['width' => '100%', 'height' => '135px']; // Image style
public $multipleFileUpload = false; // Toggle multiple uploads
public $imageUrl; // URL for single image preview
public $fileList = []; // Array for multiple image previews
public $loading = false; // Loading state (not used extensively here)
// Mount method to initialize props from the view
public function mount($name, $label, $required = false, $limit100kb = false, $imgStyle = ['width' => '100%', 'height' => '135px'], $multipleFileUpload = false, $imageUrl = null)
{
$this->name = $name;
$this->label = $label;
$this->required = $required;
$this->limit100kb = $limit100kb;
$this->imgStyle = $imgStyle;
$this->multipleFileUpload = $multipleFileUpload;
$this->imageUrl = $imageUrl;
}
// Validation and preview logic when the image is updated
public function updatedImage()
{
$this->validate([
'image' => 'required|image|mimes:jpeg,png,gif|max:' . ($this->limit100kb ? '100' : '2048'), // 100KB or 2MB
]);
if ($this->multipleFileUpload) {
$this->fileList = array_map(function ($file) {
return $file->temporaryUrl();
}, $this->image);
} else {
$this->imageUrl = $this->image->temporaryUrl();
}
// Dispatch an event to notify parent components (similar to handleFileUpload in React)
$this->dispatch('imageUploaded', $this->image);
}
public function render()
{
return view('livewire.upload-image');
}
}

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

View File

@ -45,11 +45,11 @@ return [
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'host' => env('DB_HOST', 'mysql'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'database' => env('DB_DATABASE', 'laravel-cms'),
'username' => env('DB_USERNAME', 'laravel_user'),
'password' => env('DB_PASSWORD', 'password'),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),

80
docker-compose.yml Normal file
View File

@ -0,0 +1,80 @@
version: '3.8'
services:
# Laravel App
app:
build:
context: ./docker/php
dockerfile: Dockerfile
container_name: laravel-app
restart: always
working_dir: /var/www
volumes:
- .:/var/www
depends_on:
db_mysql:
condition: service_healthy
command: >
/bin/sh -c '
mkdir -p /var/www/storage /var/www/bootstrap/cache &&
chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache &&
chmod -R 775 /var/www/storage /var/www/bootstrap/cache &&
composer install --no-dev --optimize-autoloader &&
php artisan migrate --force &&
php-fpm '
healthcheck:
test: ["CMD", "sh", "-c", "pgrep php-fpm"]
interval: 30s
timeout: 10s
retries: 10
networks:
- app_network
# MySQL
db_mysql:
image: mysql:8.0
container_name: db_mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: newpassword
MYSQL_DATABASE: laravel-cms
MYSQL_USER: laravel_user
MYSQL_PASSWORD: password
MYSQL_ALLOW_EMPTY_PASSWORD: "no"
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-pnewpassword"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app_network
# Nginx
web:
image: nginx:1.26.3-alpine
container_name: web
restart: always
ports:
- "8000:80"
volumes:
- .:/var/www
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:rw
depends_on:
app:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 30s
timeout: 10s
retries: 5
networks:
- app_network
volumes:
mysql-data:
networks:
app_network:
driver: bridge

28
docker/nginx/default.conf Normal file
View File

@ -0,0 +1,28 @@
server {
listen 80;
server_name localhost;
root /var/www/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi.conf;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
# Deny access to .htaccess files
location ~ /\.ht {
deny all;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}

41
docker/php/Dockerfile Normal file
View File

@ -0,0 +1,41 @@
# Use PHP 8.2 Alpine (compatible with Laravel 11)
FROM php:8.2-fpm-alpine
# Install required dependencies
RUN apk add --no-cache \
oniguruma-dev \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
freetype-dev \
libzip-dev \
zip \
unzip \
openssl # Laravel requires OpenSSL for encryption
# Install required extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install gd pdo pdo_mysql bcmath mbstring zip
# Install Composer (using official Composer image)
COPY --from=composer:2.7 /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www
# Copy Laravel application files BEFORE running composer install
COPY . /var/www/
# Ensure composer.json exists before running install
RUN if [ -f "composer.json" ]; then composer install --no-dev --optimize-autoloader; else echo "composer.json not found!"; fi
# Ensure required Laravel directories exist and set permissions
RUN mkdir -p /var/www/storage /var/www/bootstrap/cache && \
chown -R www-data:www-data /var/www && \
chmod -R 775 /var/www/storage /var/www/bootstrap/cache
# Expose PHP-FPM port
EXPOSE 9000
# Start PHP-FPM server
CMD ["php-fpm"]

34
docker/php/bootstrap/cache/packages.php vendored Executable file
View File

@ -0,0 +1,34 @@
<?php return array (
'laravel/tinker' =>
array (
'providers' =>
array (
0 => 'Laravel\\Tinker\\TinkerServiceProvider',
),
),
'livewire/livewire' =>
array (
'aliases' =>
array (
'Livewire' => 'Livewire\\Livewire',
),
'providers' =>
array (
0 => 'Livewire\\LivewireServiceProvider',
),
),
'nesbot/carbon' =>
array (
'providers' =>
array (
0 => 'Carbon\\Laravel\\ServiceProvider',
),
),
'nunomaduro/termwind' =>
array (
'providers' =>
array (
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
),
),
);

248
docker/php/bootstrap/cache/services.php vendored Executable file
View File

@ -0,0 +1,248 @@
<?php return array (
'providers' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
2 => 'Illuminate\\Bus\\BusServiceProvider',
3 => 'Illuminate\\Cache\\CacheServiceProvider',
4 => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
5 => 'Illuminate\\Concurrency\\ConcurrencyServiceProvider',
6 => 'Illuminate\\Cookie\\CookieServiceProvider',
7 => 'Illuminate\\Database\\DatabaseServiceProvider',
8 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
9 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
10 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
11 => 'Illuminate\\Hashing\\HashServiceProvider',
12 => 'Illuminate\\Mail\\MailServiceProvider',
13 => 'Illuminate\\Notifications\\NotificationServiceProvider',
14 => 'Illuminate\\Pagination\\PaginationServiceProvider',
15 => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
16 => 'Illuminate\\Pipeline\\PipelineServiceProvider',
17 => 'Illuminate\\Queue\\QueueServiceProvider',
18 => 'Illuminate\\Redis\\RedisServiceProvider',
19 => 'Illuminate\\Session\\SessionServiceProvider',
20 => 'Illuminate\\Translation\\TranslationServiceProvider',
21 => 'Illuminate\\Validation\\ValidationServiceProvider',
22 => 'Illuminate\\View\\ViewServiceProvider',
23 => 'Laravel\\Tinker\\TinkerServiceProvider',
24 => 'Livewire\\LivewireServiceProvider',
25 => 'Carbon\\Laravel\\ServiceProvider',
26 => 'Termwind\\Laravel\\TermwindServiceProvider',
27 => 'App\\Providers\\AppServiceProvider',
),
'eager' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Cookie\\CookieServiceProvider',
2 => 'Illuminate\\Database\\DatabaseServiceProvider',
3 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
4 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
5 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
6 => 'Illuminate\\Notifications\\NotificationServiceProvider',
7 => 'Illuminate\\Pagination\\PaginationServiceProvider',
8 => 'Illuminate\\Session\\SessionServiceProvider',
9 => 'Illuminate\\View\\ViewServiceProvider',
10 => 'Livewire\\LivewireServiceProvider',
11 => 'Carbon\\Laravel\\ServiceProvider',
12 => 'Termwind\\Laravel\\TermwindServiceProvider',
13 => 'App\\Providers\\AppServiceProvider',
),
'deferred' =>
array (
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Bus\\BatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Bus\\DatabaseBatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
'cache' => 'Illuminate\\Cache\\CacheServiceProvider',
'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider',
'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider',
'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider',
'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider',
'Illuminate\\Foundation\\Console\\AboutCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\ForgetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Auth\\Console\\ClearResetsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\DbCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\PruneCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\ShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\WipeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\DownCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnvironmentCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnvironmentDecryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnvironmentEncryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Concurrency\\Console\\InvokeSerializedClosureCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\OptimizeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\OptimizeClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\PackageDiscoverCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\PruneStaleTagsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ListFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\FlushFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\DumpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Seeds\\SeedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleClearCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleTestCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleWorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleInterruptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\ShowModelCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\StorageLinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\StorageUnlinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\UpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ViewCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ViewClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ApiInstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\BroadcastingInstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\CacheTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\CastMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ChannelListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ClassMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ComponentMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConsoleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Routing\\Console\\ControllerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\DocsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnumMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ExceptionMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Factories\\FactoryMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\InterfaceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\JobMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\JobMiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\LangPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ListenerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\MailMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Routing\\Console\\MiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ModelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\NotificationMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Notifications\\Console\\NotificationTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ObserverMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\PolicyMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ProviderMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\FailedTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\BatchesTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RequestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ResourceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RuleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ScopeMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Seeds\\SeederMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Session\\Console\\SessionTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ServeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\StubPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\TestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\TraitMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\VendorPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ViewMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\FreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\StatusCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\MigrateMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Concurrency\\ConcurrencyManager' => 'Illuminate\\Concurrency\\ConcurrencyServiceProvider',
'hash' => 'Illuminate\\Hashing\\HashServiceProvider',
'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider',
'mail.manager' => 'Illuminate\\Mail\\MailServiceProvider',
'mailer' => 'Illuminate\\Mail\\MailServiceProvider',
'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider',
'auth.password' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
'auth.password.broker' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
'pipeline' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
'queue' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.listener' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.worker' => 'Illuminate\\Queue\\QueueServiceProvider',
'redis' => 'Illuminate\\Redis\\RedisServiceProvider',
'redis.connection' => 'Illuminate\\Redis\\RedisServiceProvider',
'translator' => 'Illuminate\\Translation\\TranslationServiceProvider',
'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider',
'validator' => 'Illuminate\\Validation\\ValidationServiceProvider',
'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider',
'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => 'Illuminate\\Validation\\ValidationServiceProvider',
'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider',
),
'when' =>
array (
'Illuminate\\Broadcasting\\BroadcastServiceProvider' =>
array (
),
'Illuminate\\Bus\\BusServiceProvider' =>
array (
),
'Illuminate\\Cache\\CacheServiceProvider' =>
array (
),
'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider' =>
array (
),
'Illuminate\\Concurrency\\ConcurrencyServiceProvider' =>
array (
),
'Illuminate\\Hashing\\HashServiceProvider' =>
array (
),
'Illuminate\\Mail\\MailServiceProvider' =>
array (
),
'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider' =>
array (
),
'Illuminate\\Pipeline\\PipelineServiceProvider' =>
array (
),
'Illuminate\\Queue\\QueueServiceProvider' =>
array (
),
'Illuminate\\Redis\\RedisServiceProvider' =>
array (
),
'Illuminate\\Translation\\TranslationServiceProvider' =>
array (
),
'Illuminate\\Validation\\ValidationServiceProvider' =>
array (
),
'Laravel\\Tinker\\TinkerServiceProvider' =>
array (
),
),
);

File diff suppressed because it is too large Load Diff

49
package-lock.json generated
View File

@ -4,13 +4,17 @@
"requires": true,
"packages": {
"": {
"dependencies": {
"bootstrap": "^5.3.5",
"bootstrap-icons": "^1.11.3"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"tailwindcss": "^3.4.17",
"vite": "^6.0.11"
}
},
@ -536,6 +540,16 @@
"node": ">=14"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.39.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz",
@ -926,6 +940,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bootstrap": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz",
"integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",

View File

@ -11,7 +11,11 @@
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"tailwindcss": "^3.4.17",
"vite": "^6.0.11"
},
"dependencies": {
"bootstrap": "^5.3.5",
"bootstrap-icons": "^1.11.3"
}
}

View File

@ -1,3 +1,6 @@
@import 'bootstrap/dist/css/bootstrap.min.css';
.table-operations > button {
margin-right: 8px;
}
@ -17,3 +20,18 @@
.alert-success { background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d; }
.alert-danger { background-color: #f2dede; border-color: #ebccd1; color: #a94442; }
.alert-info { background-color: #cce5ff; border-color: #b8daff; color: #004085; }
.error-404 {
text-align: center;
margin-top: 10%;
}
.error-404 h1 {
font-size: 150px;
font-weight: bold;
margin: 0;
}
.error-404 p:first-of-type {
font-size: 30px;
font-weight: bold;
margin: 0;
}

BIN
public/img/bg_cms.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

1
public/img/ic_error.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.834 57.955"><defs><style>.a{fill:#f4825c;stroke:#707070;}.b{fill:#e74610;}.c{stroke:none;}.d{fill:none;}</style></defs><g transform="translate(-0.498 0.199)"><g transform="translate(0 0)"><g class="a" transform="translate(1.009 0.934)"><circle class="c" cx="27.989" cy="27.989" r="27.989"/><circle class="d" cx="27.989" cy="27.989" r="27.489"/></g><g transform="translate(-26.502 -27.6)"><g transform="translate(27 27.4)"><path class="b" d="M55.917,85.355A28.978,28.978,0,1,1,84.834,56.317,29.012,29.012,0,0,1,55.917,85.355Zm0-54.31A25.272,25.272,0,1,0,81.189,56.317,25.253,25.253,0,0,0,55.917,31.045Z" transform="translate(-27 -27.4)"/><g transform="translate(18.529 18.65)"><path class="b" d="M59.638,63.344a1.827,1.827,0,0,1-1.337-.486l-8.5-8.5a1.762,1.762,0,0,1,0-2.551l8.5-8.5a1.8,1.8,0,0,1,2.551,2.551l-7.168,7.168,7.168,7.169a1.762,1.762,0,0,1,0,2.551A1.234,1.234,0,0,1,59.638,63.344Z" transform="translate(-40.745 -42.75)"/><path class="b" d="M44.133,63.323a1.827,1.827,0,0,1-1.336-.486,1.762,1.762,0,0,1,0-2.551l7.169-7.169L42.8,45.948A1.8,1.8,0,0,1,45.348,43.4l8.5,8.5a1.762,1.762,0,0,1,0,2.551l-8.5,8.5A1.814,1.814,0,0,1,44.133,63.323Z" transform="translate(-42.25 -42.729)"/></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58.184 58.184"><defs><style>.a{fill:#56b68b;stroke:#707070;}.b{fill:#018952;fill-rule:evenodd;}.c{stroke:none;}.d{fill:none;}</style></defs><g transform="translate(-138.502 -248.613)"><g transform="translate(138.502 248.613)"><g transform="translate(0 0)"><g class="a" transform="translate(1.009 0.934)"><circle class="c" cx="27.989" cy="27.989" r="27.989"/><circle class="d" cx="27.989" cy="27.989" r="27.489"/></g><path class="b" d="M47.664,22.747l2.3,2.3L31.8,43.209l-2.3,2.3-2.3-2.3-8.978-8.978,2.3-2.3L29.5,40.913ZM34.092,5A29.092,29.092,0,1,1,5,34.092,29.079,29.079,0,0,1,34.092,5Zm0,3.967A25.16,25.16,0,1,1,8.967,34.092,25.173,25.173,0,0,1,34.092,8.967Z" transform="translate(-5 -5)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 763 B

BIN
public/img/logo_unioil.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Advanced Search Filter</h1>
@livewire('advance-search-filter')
</div>
@endsection

View File

@ -1,5 +1,8 @@
@extends('layouts.app')
@section('content')
@livewire($component, $props)
<div class="container my-5">
<h1 class="mb-4">Advance Table</h1>
@livewire('advance-table', ['props' => ['columns' => [], 'url' => [], 'keyValue' => 'id']])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Cascader Form</h1>
@livewire('cascader-form', ['url' => 'your-api-endpoint'])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Checkbox Form</h1>
@livewire('checkbox-form', ['name' => 'agree', 'label' => 'I Agree', 'inline' => false, 'required' => true])
</div>
@endsection

View File

@ -0,0 +1,20 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Custom Table</h1>
@livewire('custom-table', [
'url' => 'https://your-api-endpoint',
'columns' => [
['title' => 'Name', 'dataIndex' => 'name'],
['title' => 'Email', 'dataIndex' => 'email'],
],
'actions' => [
['type' => 'edit', 'path' => '/edit'],
['type' => 'view', 'path' => '/view'],
['type' => 'delete'],
],
'keyValue' => 'id'
])
</div>
@endsection

View File

@ -0,0 +1,14 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Data List</h1>
@livewire('data-list', [
'url' => 'your-api-endpoint',
'avatar' => true,
'viewPath' => '/view/:id',
'header' => 'List Header',
'footer' => 'List Footer'
])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Date Picker Form</h1>
@livewire('date-picker-form', ['name' => 'date', 'label' => 'Select Date', 'type' => 'date', 'required' => true, 'minDateToday' => true])
</div>
@endsection

View File

@ -1,5 +1,7 @@
@extends('layouts.app')
@section('content')
@livewire($component)
<div class="container my-5">
@livewire('page403')
</div>
@endsection

View File

@ -0,0 +1,7 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
@livewire('page404-new')
</div>
@endsection

View File

@ -1,5 +1,7 @@
@extends('layouts.app')
@section('content')
@livewire($component)
<div class="container my-5">
@livewire('page404')
</div>
@endsection

View File

@ -0,0 +1,7 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
@livewire('header-form', ['title' => 'Form Header', 'actionBtnName' => 'Save', 'cancelBtnName' => 'Cancel', 'deleteBtnName' => 'Delete', 'withConfirm' => true, 'isDropDown' => true])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Text Input</h1>
@livewire('input-form', ['name' => 'username', 'label' => 'Username', 'required' => true, 'isCopyUsername' => true])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Input Mask Number Form</h1>
@livewire('input-mask-number-form', ['name' => 'phone', 'label' => 'Phone Number', 'required' => true, 'mask' => '999-999-9999'])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Input Number Form</h1>
@livewire('input-number-ant-d', ['name' => 'quantity', 'label' => 'Quantity', 'required' => true, 'min' => 0, 'max' => 100, 'step' => 1])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Password Input</h1>
@livewire('input-password', ['name' => 'password', 'label' => 'Password', 'required' => true])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Text Area Input</h1>
@livewire('input-text-area', ['name' => 'description', 'label' => 'Description', 'required' => true, 'onCountText' => true, 'charsperpage' => 100, 'hasIcon' => true])
</div>
@endsection

View File

@ -4,11 +4,13 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
@livewireStyles
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
@yield('content')
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
@livewireScripts
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Login - Laravel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
@livewireStyles
</head>
<body>
@livewire('login-layout')
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
@livewireScripts
</body>
</html>

View File

@ -0,0 +1,33 @@
<form wire:submit.prevent="submit" class="container py-4">
<div class="row g-3">
<div class="col-md-4">
<label for="filterField" class="form-label">Filter by Code</label>
<select wire:model="filterField" class="form-select" id="filterField" required>
<option value="ff0000">Red</option>
<option value="00ff00">Green</option>
<option value="0000ff">Blue</option>
</select>
@error('filterField')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>
<div class="col-md-4">
<label class="form-label">Filter by Date Range</label>
<div class="row g-2">
<div class="col">
<input type="date" wire:model="filterValueStart" class="form-control" placeholder="From">
</div>
<div class="col">
<input type="date" wire:model="filterValueEnd" class="form-control" placeholder="To">
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col text-end">
<button type="submit" class="btn btn-primary me-2">Search</button>
<button type="button" wire:click="resetForm" class="btn btn-secondary">Clear</button>
</div>
</div>
</form>

View File

@ -1,47 +1,47 @@
<div style="margin: 0 24px; padding: 24px 0">
<div class="container mx-auto py-6">
@if (session()->has('success'))
<div class="alert alert-success">{{ session('success') }}</div>
<div class="alert alert-success mb-4">{{ session('success') }}</div>
@endif
@if (session()->has('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
<div class="alert alert-danger mb-4">{{ session('error') }}</div>
@endif
@if (session()->has('info'))
<div class="alert alert-info">{{ session('info') }}</div>
<div class="alert alert-info mb-4">{{ session('info') }}</div>
@endif
<div wire:loading wire:target="fetchData" class="loading-overlay">
<div wire:loading wire:target="fetchData" class="position-fixed top-0 start-0 w-100 h-100 bg-dark bg-opacity-50 text-white d-flex align-items-center justify-content-center" style="z-index: 1050;">
Loading...
</div>
<div class="row-type-flex justify-space-between align-bottom" style="padding-bottom: 25px">
<div>
<input type="text" wire:model.debounce.1000ms="searchFilter" style="width: 300px"
placeholder="Search" class="ant-input">
<span class="ant-input-prefix" style="color: rgba(0,0,0,.25)">
<i class="anticon anticon-search"></i>
</span>
</div>
<div class="table-operations">
<button wire:click="clearAll" class="ant-btn">Clear filters</button>
<div class="row justify-content-between align-items-end pb-4">
<div class="col-auto">
@if ($url['csv'])
<div>Export Dropdown Placeholder</div>
<input type="text" placeholder="Date Range Picker Placeholder" class="form-control" style="width: 300px;">
@else
<div class="input-group" style="width: 300px;">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" wire:model.debounce.1000ms="searchFilter" class="form-control" placeholder="Search">
</div>
@endif
</div>
<div class="col-auto">
<button wire:click="clearAll" class="btn btn-secondary me-2">Clear filters</button>
@if ($url['csv'])
<span>Export Dropdown Placeholder</span>
@endif
</div>
</div>
<table class="ant-table ant-table-middle" wire:loading.class="loading">
<thead>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
@foreach($columns as $column)
<th wire:click="setSort('{{ $column['dataIndex'] }}')"
class="{{ $sortBy === $column['dataIndex'] ? 'ant-table-column-sort' : '' }}
{{ $sortOrder === 'asc' ? 'ascend' : 'descend' }}">
<th wire:click="setSort('{{ $column['dataIndex'] }}')" class="cursor-pointer {{ $sortBy === $column['dataIndex'] ? 'bg-primary-subtle' : '' }}">
{{ $column['title'] }}
<span class="ant-table-column-sorter">
<span class="ant-table-column-sorter-inner">
<i class="anticon anticon-caret-up"></i>
<i class="anticon anticon-caret-down"></i>
</span>
<span>
<i class="bi bi-caret-up-fill {{ $sortOrder === 'asc' ? 'text-primary' : 'text-muted' }}"></i>
<i class="bi bi-caret-down-fill {{ $sortOrder === 'desc' ? 'text-primary' : 'text-muted' }}"></i>
</span>
</th>
@endforeach
@ -60,13 +60,9 @@
<td>
@foreach($column['buttons'] ?? [] as $action)
@if($action['key'] === 'delete' && $record['editable'] !== false)
<button wire:click="delete({{ $record[$keyValue] }})" class="ant-btn ant-btn-danger">
Delete
</button>
@elseif(in_array($action['key'], ['edit', 'view', 'location']))
<button class="ant-btn ant-btn-primary" wire:click="redirect('{{ $action['key'] }}', {{ $record[$keyValue] }})">
{{ ucfirst($action['key']) }}
</button>
<button wire:click="delete({{ $record[$keyValue] }})" class="btn btn-danger btn-sm me-1">Delete</button>
@elseif(in_array($action['key'], ['edit', 'view']))
<button wire:click="redirect('{{ $action['key'] }}', {{ $record[$keyValue] }})" class="btn btn-primary btn-sm me-1">{{ ucfirst($action['key']) }}</button>
@endif
@endforeach
</td>
@ -75,45 +71,31 @@
@endforeach
</tbody>
</table>
</div>
@if($total > 0)
<div class="row-type-flex justify-space-between" style="margin-top: 20px">
<div>
<div class="row justify-content-between align-items-center mt-4">
<div class="col-auto">
@if($apiDelete)
<button wire:click="handleBatchDelete" class="ant-btn ant-btn-danger" disabled="{{ count($selectedRowKeys) === 0 }}">
<button wire:click="handleBatchDelete" class="btn btn-danger" {{ count($selectedRowKeys) === 0 ? 'disabled' : '' }}>
Delete
</button>
<span style="margin-left: 8px">
<span class="ms-2">
{{ count($selectedRowKeys) > 0 ? "Selected " . count($selectedRowKeys) . " item(s)" : '' }}
</span>
@endif
</div>
<div>
<ul class="ant-pagination">
<div class="col-auto">
<nav aria-label="Page navigation">
<ul class="pagination">
@for($i = 1; $i <= ceil($total / $perPage); $i++)
<li class="{{ $currentPage === $i ? 'ant-pagination-item-active' : 'ant-pagination-item' }}" wire:click="updatePage({{ $i }})">
{{ $i }}
<li class="page-item {{ $currentPage === $i ? 'active' : '' }}">
<button wire:click="updatePage({{ $i }})" class="page-link">{{ $i }}</button>
</li>
@endfor
</ul>
</nav>
</div>
</div>
@endif
</div>
<style>
.row-type-flex { display: flex; justify-content: space-between; align-items: bottom; }
.justify-space-between { justify-content: space-between; }
.align-bottom { align-items: bottom; }
.table-operations > button { margin-right: 8px; }
.ant-table { width: 100%; border-collapse: collapse; }
.ant-table th, .ant-table td { border: 1px solid #f0f0f0; padding: 8px; text-align: left; }
.ant-table th { background: #fafafa; cursor: pointer; }
.ant-table-column-sort { background: #e6f7ff; }
.ant-btn { padding: 0 15px; height: 32px; border: 1px solid #d9d9d9; border-radius: 4px; cursor: pointer; }
.ant-btn-primary { background: #1890ff; color: white; border-color: #1890ff; }
.ant-btn-danger { background: #ff4d4f; color: white; border-color: #ff4d4f; }
.loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); color: white; text-align: center; padding-top: 20%; z-index: 1000; }
.ant-input { border: 1px solid #d9d9d9; border-radius: 4px; padding: 4px 11px; }
.ant-input-prefix { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); }
</style>

View File

@ -0,0 +1,22 @@
<div class="mb-3">
@if (session()->has('error'))
<div class="alert alert-danger mb-2">{{ session('error') }}</div>
@endif
<label for="cascader" class="form-label">Select Location</label>
<select wire:model="selected" class="form-select" id="cascader" multiple>
@foreach($options as $option)
<optgroup label="{{ $option['label'] }}">
@foreach($option['children'] as $child)
<option value="{{ $child['value'] }}">{{ $child['label'] }}</option>
@foreach($child['children'] ?? [] as $grandchild)
<option value="{{ $grandchild['value'] }}">{{ $grandchild['label'] }}</option>
@endforeach
@endforeach
</optgroup>
@endforeach
</select>
@error('selected')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,7 @@
<div class="{{ $inline ? 'form-check form-check-inline' : 'form-check mb-3' }}" style="{{ !$inline ? 'margin-left: 20.9%;' : '' }}">
<input type="checkbox" wire:model="value" class="form-check-input" id="{{ $name }}" {{ $required ? 'required' : '' }}>
<label for="{{ $name }}" class="form-check-label">{{ $label }}</label>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,75 @@
<div>
<!-- Search Input -->
<div class="mb-3">
<input
type="text"
wire:model.debounce.500ms="searchValue"
class="form-control"
placeholder="Search..."
>
</div>
<!-- Table -->
<table class="table table-bordered table-hover">
<thead>
<tr>
@foreach($columns as $column)
<th wire:click="sort('{{ $column['dataIndex'] }}')" style="cursor: pointer;">
{{ $column['title'] }}
@if($sortedInfo && $sortedInfo['field'] === $column['dataIndex'])
<i class="bi bi-caret-{{ $sortedInfo['order'] === 'asc' ? 'up' : 'down' }}-fill"></i>
@endif
</th>
@endforeach
@if($actions)
<th>Action</th>
@endif
</tr>
</thead>
<tbody>
@forelse($data as $item)
<tr>
@foreach($columns as $column)
<td>{{ $item[$column['dataIndex']] }}</td>
@endforeach
@if($actions)
<td>
@foreach($actions as $action)
@if($action['type'] === 'edit')
<a href="{{ $action['path'] . '/' . $item[$keyValue] }}" class="btn btn-sm btn-primary me-1">Edit</a>
@elseif($action['type'] === 'view')
<a href="{{ $action['path'] . '/' . $item[$keyValue] }}" class="btn btn-sm btn-info me-1">View</a>
@elseif($action['type'] === 'delete')
<button class="btn btn-sm btn-danger" wire:click="$dispatch('delete', {{ $item[$keyValue] }})">Delete</button>
@endif
@endforeach
</td>
@endif
</tr>
@empty
<tr>
<td colspan="{{ count($columns) + ($actions ? 1 : 0) }}" class="text-center">No data available</td>
</tr>
@endforelse
</tbody>
</table>
<!-- Pagination and Clear Filters -->
<div class="d-flex justify-content-between align-items-center mt-3">
<button wire:click="handleClearAll" class="btn btn-secondary">Clear Filters</button>
<nav>
<ul class="pagination">
@for($i = 1; $i <= ceil($totalData / $pageSize); $i++)
<li class="page-item {{ $currentPage === $i ? 'active' : '' }}">
<button wire:click="handlePagination({{ $i }})" class="page-link">{{ $i }}</button>
</li>
@endfor
</ul>
</nav>
</div>
<!-- Loading Indicator -->
@if($loading)
<div class="text-center my-3">Loading...</div>
@endif
</div>

View File

@ -0,0 +1,27 @@
<div>
@if(session()->has('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
@if($header)
<div class="mb-3">{{ $header }}</div>
@endif
<ul class="list-group">
@foreach($data as $item)
<li class="list-group-item d-flex align-items-center">
@if($avatar)
<img src="{{ $item['avatar'] }}" alt="Avatar" class="rounded-circle me-3" style="width: 40px; height: 40px;">
@endif
<div>
<a href="{{ str_replace(':id', $item['id'], $viewPath) }}" class="text-primary">{{ $item['first_name'] }}</a>
<p class="mb-0 text-muted">{{ strtolower($item['first_name']) . '_' . strtolower($item['last_name']) . '@gmail.com' }}</p>
</div>
</li>
@endforeach
</ul>
@if($footer)
<div class="mt-3">{{ $footer }}</div>
@endif
</div>

View File

@ -0,0 +1,18 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
@if($type === 'range')
<div class="row g-2">
<div class="col">
<input type="date" wire:model="value.0" class="form-control" id="{{ $name }}_start" {{ $required ? 'required' : '' }} min="{{ $minDate }}">
</div>
<div class="col">
<input type="date" wire:model="value.1" class="form-control" id="{{ $name }}_end" {{ $required ? 'required' : '' }} min="{{ $minDate }}">
</div>
</div>
@else
<input type="{{ $type === 'date-time' ? 'datetime-local' : 'date' }}" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }} min="{{ $minDate }}">
@endif
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,19 @@
<div>
<button
wire:click="handleExportCSV"
class="btn"
style="background: rgb(231, 70, 16); border-color: rgb(231, 70, 16); color: #fff;"
wire:loading.attr="disabled"
>
@if($loading)
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Exporting...
@else
Export CSV
@endif
</button>
@if(session()->has('error'))
<div class="alert alert-danger mt-2">{{ session('error') }}</div>
@endif
</div>

View File

@ -0,0 +1,95 @@
<div class="d-flex justify-content-between align-items-center border-bottom bg-white position-fixed w-100 py-2 px-4" style="z-index: 99; top: {{ $isInsideForm ? '-154px' : '-73px' }}; left: -17px;">
<h1 class="fs-4 mb-0">{{ $title }}</h1>
<div class="d-flex position-fixed" style="right: 24px;">
@if($actionBtnName)
@if($withConfirm)
<button type="button" class="btn btn-primary me-2" style="width: 135px;" wire:click="action" {{ $loading || $disabled ? 'disabled' : '' }} data-bs-toggle="modal" data-bs-target="#confirmModal">
{{ $actionBtnName }}
</button>
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmModalLabel">Confirmation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
<button type="button" class="btn btn-primary" wire:click="action" data-bs-dismiss="modal">Yes</button>
</div>
</div>
</div>
</div>
@else
@if(!$isDropDown)
<button type="button" class="btn btn-primary me-2" style="width: 135px;" wire:click="action" {{ $loading || $disabled ? 'disabled' : '' }}>
{{ $actionBtnName }}
</button>
@endif
@endif
@endif
@if($cancelBtnName)
<button type="button" class="btn btn-outline-secondary me-2" style="width: 135px;" wire:click="cancel" {{ $loading ? 'disabled' : '' }} {{ $withCancelConfirm ? 'data-bs-toggle=modal data-bs-target=#cancelModal' : '' }}>
{{ $cancelBtnName }}
</button>
@if($withCancelConfirm)
<div class="modal fade" id="cancelModal" tabindex="-1" aria-labelledby="cancelModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="cancelModalLabel">Cancel Confirmation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to cancel?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
<button type="button" class="btn btn-primary" wire:click="cancel" data-bs-dismiss="modal">Yes</button>
</div>
</div>
</div>
</div>
@endif
@endif
@if($deleteBtnName)
<button type="button" class="btn btn-outline-danger me-2" style="width: 135px;" wire:click="deleteAction" {{ $loading || $disabled ? 'disabled' : '' }} data-bs-toggle="modal" data-bs-target="#deleteModal">
{{ $deleteBtnName }}
</button>
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete Confirmation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete this record?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button>
<button type="button" class="btn btn-danger" wire:click="deleteAction" data-bs-dismiss="modal">Yes</button>
</div>
</div>
</div>
</div>
@endif
@if($isDropDown)
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" {{ $disabled ? 'disabled' : '' }}>
Add
</button>
<ul class="dropdown-menu">
<li><button class="dropdown-item" wire:click="actionPrivacy">Terms & Condition</button></li>
<li><button class="dropdown-item" wire:click="actionTerms">Privacy Policy</button></li>
</ul>
</div>
@endif
</div>
</div>

View File

@ -0,0 +1,30 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
@if(!$withActionBtn)
<div class="input-group">
<input type="text" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }}>
@if($isCopyUsername)
<button class="btn btn-primary" type="button" wire:click="$dispatch('copy', '{{ $value }}')" {{ $value ? '' : 'disabled' }} onclick="navigator.clipboard.writeText('{{ $value }}'); alert('Copied to clipboard!');">
Copy
</button>
@endif
</div>
@else
<div class="row g-2">
<div class="col-md-6">
<input type="text" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }}>
</div>
<div class="col-md-6">
<button class="btn btn-primary me-2" type="button" wire:click="$dispatch('action')" {{ $loading ? 'disabled' : '' }}>
Action
</button>
<button class="btn btn-primary" type="button" wire:click="$dispatch('copy', '{{ $value }}')" {{ $value ? '' : 'disabled' }} onclick="navigator.clipboard.writeText('{{ $value }}'); alert('Copied to clipboard!');">
Copy
</button>
</div>
</div>
@endif
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,33 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<input type="text" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }} data-mask="{{ $mask }}">
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>
<script>
document.addEventListener('livewire:load', function () {
const inputs = document.querySelectorAll('[data-mask]');
inputs.forEach(input => {
const mask = input.getAttribute('data-mask');
input.addEventListener('input', function (e) {
let value = e.target.value.replace(/\D/g, '');
let formatted = '';
let maskIndex = 0;
for (let i = 0; i < value.length && maskIndex < mask.length; i++) {
if (mask[maskIndex] === '9') {
formatted += value[i];
maskIndex++;
} else {
formatted += mask[maskIndex];
maskIndex++;
i--; // Stay on the same digit
}
}
e.target.value = formatted;
@this.set('value', formatted);
});
});
});
</script>

View File

@ -0,0 +1,7 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<input type="number" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }} min="{{ $min }}" max="{{ $max }}" step="{{ $step }}">
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,7 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<input type="password" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }}>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,30 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">
{{ $label }}
@if($hasIcon)
<span class="ms-1" data-bs-toggle="tooltip" data-bs-placement="top" title="This content will be used in the 'Enter ID Number' page as part of the Apply for a Card process of the Unioil Mobile App.">
<i class="bi bi-question-circle"></i>
</span>
@endif
</label>
<textarea wire:model="value" class="form-control" id="{{ $name }}" rows="3" {{ $required ? 'required' : '' }}></textarea>
@if($onCountText)
<div class="position-relative">
<small class="position-absolute end-0 top-0 mt-1 me-2" style="color: {{ strlen($value) > $charsperpage ? 'red' : '#6c757d' }};">
{{ strlen($value) }}/{{ $charsperpage }}
</small>
</div>
@endif
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
});
</script>

View File

@ -0,0 +1,8 @@
<div class="d-flex justify-content-center align-items-center py-4">
<div class="text-center">
<div class="spinner-border text-primary me-2" role="status">
<span class="visually-hidden">Loading...</span>
</div>
Loading Data Please wait...
</div>
</div>

View File

@ -0,0 +1,19 @@
<div class="min-vh-100 d-flex">
<!-- Sider -->
<div class="col-md-6 d-none d-md-block" style="background: url('/img/bg_cms.png') center / cover no-repeat;"></div>
<!-- Content and Footer -->
<div class="col-md-6 d-flex flex-column justify-content-between">
<div class="p-4 flex-grow-1">
@if($children)
{{ $children }}
@else
@yield('content')
@endif
</div>
<footer class="text-center py-3" style="font-size: 12px;">
<div class="mx-auto px-0 py-4" style="width: 325px; border-top: 1px solid #e0e0e0; text-align: left; color: #8E8E93;">
By logging in you agree to Unioil's Terms of Service, <br>Privacy Policy and Content Policies.
</div>
</footer>
</div>
</div>

View File

@ -0,0 +1,33 @@
<div>
@if($dirty)
<button wire:click="showModal" class="btn btn-secondary" {{ $loading ? 'disabled' : '' }}>
{{ $name }}
</button>
@if($visible)
<div class="modal fade show d-block" id="{{ $id }}" tabindex="-1" aria-labelledby="{{ $id }}Label" style="background-color: rgba(0,0,0,0.4);">
<div class="modal-dialog modal-dialog-centered" style="max-width: 300px;">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="{{ $id }}Label">{{ $title }}</h5>
<button type="button" wire:click="handleCancel" class="btn-close" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>{{ $message }}</p>
</div>
<div class="modal-footer">
<button wire:click="handleCancel" class="btn btn-primary">No</button>
<a href="{{ $path }}" class="btn btn-secondary">Yes</a>
</div>
</div>
</div>
</div>
@endif
@else
<a href="{{ $path }}" class="{{ $loading ? 'pointer-events-none' : '' }}">
<button class="btn btn-secondary" {{ $loading ? 'disabled' : '' }}>
{{ $name }}
</button>
</a>
@endif
</div>

View File

@ -0,0 +1,14 @@
<div class="mb-3">
@if (session()->has('error'))
<div class="alert alert-danger mb-2">{{ session('error') }}</div>
@endif
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<select wire:model="value" class="form-select" id="{{ $name }}" multiple {{ $required ? 'required' : '' }}>
@foreach($optionsList as $item)
<option value="{{ $item['id'] }}">{{ $item['name'] ?? $item['first_name'] }}</option>
@endforeach
</select>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,11 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<select wire:model="value" class="form-select" id="{{ $name }}" multiple {{ $required ? 'required' : '' }}>
@foreach($optionsList as $item)
<option value="{{ $item['id'] }}">{{ $item['name'] }}</option>
@endforeach
</select>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,101 @@
<div class="container mx-auto py-6">
@if (session()->has('success'))
<div class="alert alert-success mb-4">{{ session('success') }}</div>
@endif
@if (session()->has('error'))
<div class="alert alert-danger mb-4">{{ session('error') }}</div>
@endif
@if (session()->has('info'))
<div class="alert alert-info mb-4">{{ session('info') }}</div>
@endif
<div wire:loading wire:target="fetchData" class="position-fixed top-0 start-0 w-100 h-100 bg-dark bg-opacity-50 text-white d-flex align-items-center justify-content-center" style="z-index: 1050;">
Loading...
</div>
<div class="row justify-content-between align-items-end pb-4">
<div class="col-auto">
@if ($url['csv'])
<input type="text" placeholder="Date Range Picker Placeholder" class="form-control" style="width: 300px;">
@else
<div class="input-group" style="width: 300px;">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" wire:model.debounce.1000ms="searchFilter" class="form-control" placeholder="Search">
</div>
@endif
</div>
<div class="col-auto">
<button wire:click="clearAll" class="btn btn-secondary me-2">Clear filters</button>
@if ($url['csv'])
<span>Export Dropdown Placeholder</span>
@endif
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
@foreach($columns as $column)
<th wire:click="setSort('{{ $column['dataIndex'] }}')" class="cursor-pointer {{ $sortBy === $column['dataIndex'] ? 'bg-primary-subtle' : '' }}">
{{ $column['title'] }}
<span>
<i class="bi bi-caret-up-fill {{ $sortOrder === 'asc' ? 'text-primary' : 'text-muted' }}"></i>
<i class="bi bi-caret-down-fill {{ $sortOrder === 'desc' ? 'text-primary' : 'text-muted' }}"></i>
</span>
</th>
@endforeach
@if($apiDelete)
<th>Action</th>
@endif
</tr>
</thead>
<tbody>
@foreach($data as $record)
<tr>
@foreach($columns as $column)
<td>{{ $record[$column['dataIndex']] ?? '' }}</td>
@endforeach
@if($apiDelete)
<td>
@foreach($column['buttons'] ?? [] as $action)
@if($action['key'] === 'delete' && $record['editable'] !== false)
<button wire:click="delete({{ $record[$keyValue] }})" class="btn btn-danger btn-sm me-1">Delete</button>
@elseif(in_array($action['key'], ['edit', 'view', 'location']))
<button wire:click="redirect('{{ $action['key'] }}', {{ $record[$keyValue] }})" class="btn btn-primary btn-sm me-1">{{ ucfirst($action['key']) }}</button>
@endif
@endforeach
</td>
@endif
</tr>
@endforeach
</tbody>
</table>
</div>
@if($total > 0)
<div class="row justify-content-between align-items-center mt-4">
<div class="col-auto">
@if($apiDelete)
<button wire:click="handleBatchDelete" class="btn btn-danger" {{ count($selectedRowKeys) === 0 ? 'disabled' : '' }}>
Delete
</button>
<span class="ms-2">
{{ count($selectedRowKeys) > 0 ? "Selected " . count($selectedRowKeys) . " item(s)" : '' }}
</span>
@endif
</div>
<div class="col-auto">
<nav aria-label="Page navigation">
<ul class="pagination">
@for($i = 1; $i <= ceil($total / $perPage); $i++)
<li class="page-item {{ $currentPage === $i ? 'active' : '' }}">
<button wire:click="updatePage({{ $i }})" class="page-link">{{ $i }}</button>
</li>
@endfor
</ul>
</nav>
</div>
</div>
@endif
</div>

View File

@ -1,6 +1,6 @@
<div align="center" style="margin-top: 10%">
<h1 style="font-size: 150px; font-weight: bold; margin: 0">403</h1>
<p style="font-size: 30px; font-weight: bold; margin: 0">Forbidden</p>
<p>Access to this resource on the server is denied!</p>
<a href="{{ route('locations') }}">Go Back</a>
<div class="container text-center py-5" style="margin-top: 10%;">
<h1 class="display-1 fw-bold mb-0">403</h1>
<p class="fs-2 fw-bold mb-0">Forbidden</p>
<p class="mb-3">Access to this resource on the server is denied!</p>
<a href="{{ route('locations') }}" class="btn btn-primary">Go Back</a>
</div>

View File

@ -0,0 +1,5 @@
<div class="container text-center py-5" style="margin-top: 10%;">
<h1 class="display-1 fw-bold mb-0">404</h1>
<p class="fs-2 fw-bold mb-0">Page not found</p>
<p class="mb-3">Sorry, but the page you are looking for doesn't exist</p>
</div>

View File

@ -1,5 +1,7 @@
<div align="center">
<h1 style="font-size: 150px; font-weight: bold; margin: 0">404</h1>
<p style="font-size: 30px; font-weight: bold; margin: 0">Page not found</p>
<p>Sorry, but the page you are looking for doesn't exist</p>
<div class="container text-center py-5">
<h1 class="display-1 fw-bold mb-0">404</h1>
<p class="fs-2 fw-bold mb-0">Page not found</p>
<p class="mb-3">Sorry, but the page you are looking for doesn't exist</p>
</div>

View File

@ -0,0 +1,14 @@
<div class="mb-3">
<label class="form-label">{{ $label }}</label>
<div class="d-flex flex-column">
@foreach($optionsList as $option)
<div class="form-check {{ $isRadioButton ? 'form-check-inline' : '' }}">
<input type="radio" wire:model="value" value="{{ $option['value'] }}" class="form-check-input" id="{{ $name }}_{{ $option['value'] }}" {{ $required ? 'required' : '' }} {{ $option['isDisabled'] ?? false ? 'disabled' : '' }}>
<label for="{{ $name }}_{{ $option['value'] }}" class="form-check-label">{{ $option['label'] }}</label>
</div>
@endforeach
</div>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,15 @@
<div class="mb-3">
@if (session()->has('error'))
<div class="alert alert-danger mb-2">{{ session('error') }}</div>
@endif
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<select wire:model="value" class="form-select" id="{{ $name }}" {{ $required ? 'required' : '' }}>
<option value="">Select an option</option>
@foreach($optionsList as $item)
<option value="{{ $item['value'] }}">{{ $item['label'] }}</option>
@endforeach
</select>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,15 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<input type="file" wire:model="image" class="form-control" id="{{ $name }}" accept="image/jpeg,image/png,image/gif" {{ $required ? 'required' : '' }}>
@if($image)
<img src="{{ $image->temporaryUrl() }}" alt="Preview" class="mt-2" style="width: {{ $imgStyle['width'] }}; height: {{ $imgStyle['height'] }};">
@else
<div class="border rounded p-3 text-center mt-2">
<i class="bi bi-upload"></i>
<p>Click or drag file to upload</p>
</div>
@endif
@error('image')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -1,47 +1,47 @@
<div style="margin: 0 24px; padding: 24px 0">
<div class="container mx-auto py-6">
@if (session()->has('success'))
<div class="alert alert-success">{{ session('success') }}</div>
<div class="alert alert-success mb-4">{{ session('success') }}</div>
@endif
@if (session()->has('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
<div class="alert alert-danger mb-4">{{ session('error') }}</div>
@endif
@if (session()->has('info'))
<div class="alert alert-info">{{ session('info') }}</div>
<div class="alert alert-info mb-4">{{ session('info') }}</div>
@endif
<div wire:loading wire:target="fetchData" class="loading-overlay">
<div wire:loading wire:target="fetchData" class="position-fixed top-0 start-0 w-100 h-100 bg-dark bg-opacity-50 text-white d-flex align-items-center justify-content-center" style="z-index: 1050;">
Loading...
</div>
<div class="row-type-flex justify-space-between align-bottom" style="padding-bottom: 25px">
<div>
<input type="text" wire:model.debounce.1000ms="searchFilter" style="width: 300px"
placeholder="Search" class="ant-input">
<span class="ant-input-prefix" style="color: rgba(0,0,0,.25)">
<i class="anticon anticon-search"></i>
</span>
</div>
<div class="table-operations">
<button wire:click="clearAll" class="ant-btn">Clear filters</button>
<div class="row justify-content-between align-items-end pb-4">
<div class="col-auto">
@if ($url['csv'])
<div>Export Dropdown Placeholder</div>
<input type="text" placeholder="Date Range Picker Placeholder" class="form-control" style="width: 300px;">
@else
<div class="input-group" style="width: 300px;">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" wire:model.debounce.1000ms="searchFilter" class="form-control" placeholder="Search">
</div>
@endif
</div>
<div class="col-auto">
<button wire:click="clearAll" class="btn btn-secondary me-2">Clear filters</button>
@if ($url['csv'])
<span>Export Dropdown Placeholder</span>
@endif
</div>
</div>
<table class="ant-table ant-table-middle" wire:loading.class="loading">
<thead>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
@foreach($columns as $column)
<th wire:click="setSort('{{ $column['dataIndex'] }}')"
class="{{ $sortBy === $column['dataIndex'] ? 'ant-table-column-sort' : '' }}
{{ $sortOrder === 'asc' ? 'ascend' : 'descend' }}">
<th wire:click="setSort('{{ $column['dataIndex'] }}')" class="cursor-pointer {{ $sortBy === $column['dataIndex'] ? 'bg-primary-subtle' : '' }}">
{{ $column['title'] }}
<span class="ant-table-column-sorter">
<span class="ant-table-column-sorter-inner">
<i class="anticon anticon-caret-up"></i>
<i class="anticon anticon-caret-down"></i>
</span>
<span>
<i class="bi bi-caret-up-fill {{ $sortOrder === 'asc' ? 'text-primary' : 'text-muted' }}"></i>
<i class="bi bi-caret-down-fill {{ $sortOrder === 'desc' ? 'text-primary' : 'text-muted' }}"></i>
</span>
</th>
@endforeach
@ -60,13 +60,9 @@
<td>
@foreach($column['buttons'] ?? [] as $action)
@if($action['key'] === 'delete' && $record['editable'] !== false)
<button wire:click="delete({{ $record[$keyValue] }})" class="ant-btn ant-btn-danger">
Delete
</button>
<button wire:click="delete({{ $record[$keyValue] }})" class="btn btn-danger btn-sm me-1">Delete</button>
@elseif(in_array($action['key'], ['edit', 'view', 'location']))
<button class="ant-btn ant-btn-primary" wire:click="redirect('{{ $action['key'] }}', {{ $record[$keyValue] }})">
{{ ucfirst($action['key']) }}
</button>
<button wire:click="redirect('{{ $action['key'] }}', {{ $record[$keyValue] }})" class="btn btn-primary btn-sm me-1">{{ ucfirst($action['key']) }}</button>
@endif
@endforeach
</td>
@ -75,45 +71,31 @@
@endforeach
</tbody>
</table>
</div>
@if($total > 0)
<div class="row-type-flex justify-space-between" style="margin-top: 20px">
<div>
<div class="row justify-content-between align-items-center mt-4">
<div class="col-auto">
@if($apiDelete)
<button wire:click="handleBatchDelete" class="ant-btn ant-btn-danger" disabled="{{ count($selectedRowKeys) === 0 }}">
<button wire:click="handleBatchDelete" class="btn btn-danger" {{ count($selectedRowKeys) === 0 ? 'disabled' : '' }}>
Delete
</button>
<span style="margin-left: 8px">
<span class="ms-2">
{{ count($selectedRowKeys) > 0 ? "Selected " . count($selectedRowKeys) . " item(s)" : '' }}
</span>
@endif
</div>
<div>
<ul class="ant-pagination">
<div class="col-auto">
<nav aria-label="Page navigation">
<ul class="pagination">
@for($i = 1; $i <= ceil($total / $perPage); $i++)
<li class="{{ $currentPage === $i ? 'ant-pagination-item-active' : 'ant-pagination-item' }}" wire:click="updatePage({{ $i }})">
{{ $i }}
<li class="page-item {{ $currentPage === $i ? 'active' : '' }}">
<button wire:click="updatePage({{ $i }})" class="page-link">{{ $i }}</button>
</li>
@endfor
</ul>
</nav>
</div>
</div>
@endif
</div>
<style>
.row-type-flex { display: flex; justify-content: space-between; align-items: bottom; }
.justify-space-between { justify-content: space-between; }
.align-bottom { align-items: bottom; }
.table-operations > button { margin-right: 8px; }
.ant-table { width: 100%; border-collapse: collapse; }
.ant-table th, .ant-table td { border: 1px solid #f0f0f0; padding: 8px; text-align: left; }
.ant-table th { background: #fafafa; cursor: pointer; }
.ant-table-column-sort { background: #e6f7ff; }
.ant-btn { padding: 0 15px; height: 32px; border: 1px solid #d9d9d9; border-radius: 4px; cursor: pointer; }
.ant-btn-primary { background: #1890ff; color: white; border-color: #1890ff; }
.ant-btn-danger { background: #ff4d4f; color: white; border-color: #ff4d4f; }
.loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); color: white; text-align: center; padding-top: 20%; z-index: 1000; }
.ant-input { border: 1px solid #d9d9d9; border-radius: 4px; padding: 4px 11px; }
.ant-input-prefix { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); color: rgba(0,0,0,.25); }
</style>

View File

@ -1,38 +1,39 @@
<div style="margin: 0 24px; padding: 24px 0">
<div class="container mx-auto py-6">
@if (session()->has('success'))
<div class="alert alert-success">{{ session('success') }}</div>
<div class="alert alert-success mb-4">{{ session('success') }}</div>
@endif
@if (session()->has('error'))
<div class="alert alert-danger">{{ session('error') }}</div>
@endif
@if (session()->has('info'))
<div class="alert alert-info">{{ session('info') }}</div>
<div class="alert alert-danger mb-4">{{ session('error') }}</div>
@endif
<div wire:loading wire:target="fetchData" class="loading-overlay">
<div wire:loading wire:target="fetchData" class="position-fixed top-0 start-0 w-100 h-100 bg-dark bg-opacity-50 text-white d-flex align-items-center justify-content-center" style="z-index: 1050;">
Loading...
</div>
<div style="margin-bottom: 16px; margin-top: 16px">
<input type="text" wire:model.debounce.1000ms="search" placeholder="Search" class="ant-input">
<span class="ant-input-prefix" style="color: rgba(0,0,0,.25)">
<i class="anticon anticon-search"></i>
</span>
<button wire:click="handleBatchDelete" class="ant-btn ant-btn-danger" disabled="{{ count($selectedRowKeys) === 0 }}">
<div class="row mb-4">
<div class="col-md-8">
<div class="input-group" style="width: 300px;">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="text" wire:model.debounce.1000ms="search" class="form-control" placeholder="Search">
</div>
</div>
</div>
<div class="mb-4">
<button wire:click="handleBatchDelete" class="btn btn-danger" {{ count($selectedRowKeys) === 0 ? 'disabled' : '' }}>
Delete All
</button>
<span style="margin-left: 8px">
<span class="ms-2">
{{ count($selectedRowKeys) > 0 ? "Selected " . count($selectedRowKeys) . " items" : '' }}
</span>
</div>
<table class="ant-table ant-table-middle" wire:loading.class="loading">
<thead>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
@foreach($columns as $column)
<th>
{{ $column['title'] }}
</th>
<th>{{ $column['title'] }}</th>
@endforeach
<th>Actions</th>
</tr>
@ -46,13 +47,9 @@
<td>
@foreach($column['renderActions'] ?? [] as $action)
@if($action['action_name'] === 'delete')
<button wire:click="delete('{{ $action['path'] }}', {{ $record[$keyValue] }})" class="ant-btn ant-btn-danger">
Delete
</button>
<button wire:click="delete('{{ $action['path'] }}', {{ $record[$keyValue] }})" class="btn btn-danger btn-sm me-1">Delete</button>
@else
<button class="ant-btn ant-btn-primary" wire:click="redirect('{{ $action['action_name'] }}', '{{ $action['path'] }}', {{ $record[$keyValue] }})">
{{ $action['action_name'] }}
</button>
<button wire:click="redirect('{{ $action['action_name'] }}', '{{ $action['path'] }}', {{ $record[$keyValue] }})" class="btn btn-primary btn-sm me-1">{{ ucfirst($action['action_name']) }}</button>
@endif
@endforeach
</td>
@ -60,33 +57,19 @@
@endforeach
</tbody>
</table>
</div>
@if($total > 0)
<div style="float: right; margin-top: 16px">
<ul class="ant-pagination">
<div class="d-flex justify-content-end mt-4">
<nav aria-label="Page navigation">
<ul class="pagination">
@for($i = 1; $i <= ceil($total / $perPage); $i++)
<li class="{{ $currentPage === $i ? 'ant-pagination-item-active' : 'ant-pagination-item' }}" wire:click="updatePage({{ $i }})">
{{ $i }}
<li class="page-item {{ $currentPage === $i ? 'active' : '' }}">
<button wire:click="updatePage({{ $i }})" class="page-link">{{ $i }}</button>
</li>
@endfor
</ul>
</nav>
</div>
@endif
</div>
<style>
.row-type-flex { display: flex; justify-content: space-between; align-items: bottom; }
.justify-space-between { justify-content: space-between; }
.align-bottom { align-items: bottom; }
.table-operations > button { margin-right: 8px; }
.ant-table { width: 100%; border-collapse: collapse; }
.ant-table th, .ant-table td { border: 1px solid #f0f0f0; padding: 8px; text-align: left; }
.ant-table th { background: #fafafa; cursor: pointer; }
.ant-table-column-sort { background: #e6f7ff; }
.ant-btn { padding: 0 15px; height: 32px; border: 1px solid #d9d9d9; border-radius: 4px; cursor: pointer; }
.ant-btn-primary { background: #1890ff; color: white; border-color: #1890ff; }
.ant-btn-danger { background: #ff4d4f; color: white; border-color: #ff4d4f; }
.loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); color: white; text-align: center; padding-top: 20%; z-index: 1000; }
.ant-input { border: 1px solid #d9d9d9; border-radius: 4px; padding: 4px 11px; }
.ant-input-prefix { position: absolute; left: 8px; top: 50%; transform: translateY(-50%); }
</style>

View File

@ -0,0 +1,7 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
<input type="time" wire:model="value" class="form-control" id="{{ $name }}" {{ $required ? 'required' : '' }}>
@error('value')
<div class="text-danger small">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,36 @@
<div class="mb-3">
<label for="{{ $name }}" class="form-label">{{ $label }}</label>
@if($multipleFileUpload)
<!-- Multiple File Upload -->
<div class="border rounded p-3 text-center" wire:loading.class="opacity-50">
<input type="file" wire:model="image" class="form-control" id="{{ $name }}" accept="image/jpeg,image/png,image/gif" multiple {{ $required ? 'required' : '' }}>
<i class="bi bi-upload"></i>
<p class="mt-2">Click or drag file to this area to upload</p>
<p class="small text-muted">Support for single or bulk upload.</p>
</div>
@if($fileList)
<div class="mt-2 d-flex flex-wrap">
@foreach($fileList as $url)
<img src="{{ $url }}" alt="Preview" class="img-thumbnail me-2 mb-2" style="width: 100px; height: 100px;">
@endforeach
</div>
@endif
@else
<!-- Single File Upload -->
<div class="border rounded p-3 text-center" wire:loading.class="opacity-50">
<input type="file" wire:model="image" class="form-control" id="{{ $name }}" accept="image/jpeg,image/png,image/gif" {{ $required ? 'required' : '' }}>
@if($imageUrl)
<img src="{{ $imageUrl }}" alt="Preview" class="mt-2" style="width: {{ $imgStyle['width'] }}; height: {{ $imgStyle['height'] }};">
@else
<i class="bi bi-upload"></i>
<p class="mt-2">Click or drag file to this area to upload</p>
<p class="small text-muted">Support for a single upload only.</p>
@endif
</div>
@endif
@error('image')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Loading Test</h1>
@livewire('loading')
</div>
@endsection

View File

@ -0,0 +1,9 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Locations</h1>
<p class="lead">This is a placeholder for the Locations page.</p>
<a href="{{ url('/') }}" class="btn btn-primary">Back to Home</a>
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.login')
@section('content')
<div class="container py-5">
<h1 class="text-center mb-4">Login</h1>
<p class="text-center">Placeholder for login form.</p>
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Multi Select Form</h1>
@livewire('multi-select-form', ['name' => 'items', 'label' => 'Select Items', 'required' => true, 'optionsList' => [['id' => 1, 'name' => 'Item 1'], ['id' => 2, 'name' => 'Item 2']]])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Multi Select Options</h1>
@livewire('multi-select-options', ['name' => 'fuels', 'label' => 'Select Fuels', 'required' => true, 'optionsList' => [['id' => 1, 'name' => 'Fuel 1'], ['id' => 2, 'name' => 'Fuel 2']]])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Notifications</h1>
@livewire('notification-table', ['props' => ['columns' => [], 'url' => [], 'keyValue' => 'id']])
</div>
@endsection

View File

@ -0,0 +1,16 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Select Form</h1>
@livewire('select-form', [
'name' => 'category',
'label' => 'Select Category',
'required' => true,
'optionsList' => [
['value' => '1', 'label' => 'Category 1'],
['value' => '2', 'label' => 'Category 2']
]
])
</div>
@endsection

View File

@ -0,0 +1,13 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Image Upload</h1>
@livewire('single-upload-image', [
'name' => 'avatar',
'label' => 'Upload Avatar',
'required' => true,
'limit100kb' => false
])
</div>
@endsection

View File

@ -0,0 +1,5 @@
@extends('layouts.app')
@section('content')
@livewire('modal-cancel', ['path' => '/somewhere', 'title' => 'Confirm Exit', 'message' => 'Are you sure?', 'id' => 'modal-container', 'dirty' => true, 'name' => 'Cancel', 'loading' => false])
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Station Table</h1>
@livewire('station-table', ['props' => ['columns' => [], 'url' => [], 'keyValue' => 'id']])
</div>
@endsection

View File

@ -0,0 +1,8 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Table Layout</h1>
@livewire('table-layout', ['props' => ['headers' => [], 'api' => [], 'keyValue' => 'id']])
</div>
@endsection

View File

@ -0,0 +1,19 @@
@extends('layouts.app')
@section('content')
<div class="container my-5">
<h1 class="mb-4">Test Modal</h1>
<div id="modal-container">
@livewire('modal-cancel', [
'path' => '/locations',
'title' => 'Confirm Exit',
'message' => 'Are you sure you want to leave? Unsaved changes will be lost.',
'id' => 'modal-container',
'dirty' => true,
'name' => 'Cancel',
'loading' => false
])
</div>
<p class="mt-3">Click the "Cancel" button to see the modal in action.</p>
</div>
@endsection

Some files were not shown because too many files have changed in this diff Show More