+ @if(env('LOGIN_NOTICE'))
+
+
+
+
+ @php
+ $loginNotice = env('LOGIN_NOTICE');
+ if (str_contains($loginNotice, ':::')) {
+ $noticeParts = explode(':::', $loginNotice);
+ $noticeTitle = $noticeParts[0];
+ $noticeMessage = $noticeParts[1];
+ } else {
+ $noticeTitle = null;
+ $noticeMessage = $loginNotice;
+ }
+ @endphp
+
+ @if($noticeTitle)
+ {!! $noticeTitle !!}
+ @else
+ Notice!
+ @endif
+
+
{!! $noticeMessage !!}
+
+
+
+
+ @else
+
+ @endif
+
+
+
+
+
+ @if(config('aim-admin.show_inline_alert_box', true))
+
+ @endif
+ {{ $slot }}
+
+
+
+
+
+
diff --git a/resources/views/components/layout/login.blade.php b/resources/views/components/layout/login.blade.php
new file mode 100644
index 0000000..4dbd46e
--- /dev/null
+++ b/resources/views/components/layout/login.blade.php
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
{{ config('app.name', 'Laravel') }}
+
+
+ {{--
--}}
+
+
+ @vite('resources/js/app.js')
+
+
+
+
+
+ {{ $slot }}
+
+
+@php $toastTime = config('aim-admin.flash-timer', 2000); @endphp
+@if (session('success'))
+
+@elseif((session('info')))
+
+@elseif((session('warning')))
+
+@elseif((session('error')))
+ @if(config('aim-admin.show_toast_error', true))
+
+ @endif
+@endif
+@if(config('aim-admin.show_toast_error', true))
+ @if($errors->any())
+ @foreach ($errors->all() as $error)
+
+ @endforeach
+ @endif
+@endif
+@stack('scripts')
+
+
+
diff --git a/resources/views/components/layout/main.blade.php b/resources/views/components/layout/main.blade.php
new file mode 100644
index 0000000..90ce192
--- /dev/null
+++ b/resources/views/components/layout/main.blade.php
@@ -0,0 +1,102 @@
+
+
+
+
+
+ @if(isset($title))
+
{{ env('APP_NAME').':: '.$title ?? 'AimAdmin Platform' }}
+ @else
+
{{env('APP_NAME')}}
+ @endif
+
+
+ @vite('resources/js/app.js')
+ @stack('styles')
+ @isset($injectedTop)
+ {!! $injectedTop !!}
+ @endisset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $slot }}
+
+
+ @if(config('aim-admin.back_to_top', true))
+
+
+
+ @endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@php $toastTime = config('aim-admin.flash-timer', 2000); @endphp
+@if (session('success'))
+
+@elseif((session('info')))
+
+@elseif((session('warning')))
+
+@elseif((session('error')))
+ @if(config('aim-admin.show_toast_error', true))
+
+ @endif
+@endif
+@if(config('aim-admin.show_toast_error', true))
+ @if($errors->any())
+ @foreach ($errors->all() as $error)
+
+ @endforeach
+ @endif
+@endif
+@stack('scripts')
+@isset($injectedBottom)
+ {!! $injectedBottom !!}
+@endisset
+
+
diff --git a/resources/views/components/mail/otp.blade.php b/resources/views/components/mail/otp.blade.php
new file mode 100644
index 0000000..dcb8904
--- /dev/null
+++ b/resources/views/components/mail/otp.blade.php
@@ -0,0 +1,14 @@
+@component('mail::message')
+ # Reset Password
+
+ You are receiving this email because we received a password reset request for your account.
+
+ Your OTP : {{$otp}}
+
+ This OTP will expire in {{ config('auth.passwords.'.config('auth.defaults.passwords').'.expire') }} minutes.
+
+ If you did not request a password reset, no further action is required.
+
+ Thanks,
+ {{ config('app.name') }}
+@endcomponent
diff --git a/resources/views/components/message/index.blade.php b/resources/views/components/message/index.blade.php
new file mode 100644
index 0000000..af1dc58
--- /dev/null
+++ b/resources/views/components/message/index.blade.php
@@ -0,0 +1,57 @@
+
+
+
+ 3
+
+
+
diff --git a/resources/views/components/navbar/index.blade.php b/resources/views/components/navbar/index.blade.php
new file mode 100644
index 0000000..6b875c8
--- /dev/null
+++ b/resources/views/components/navbar/index.blade.php
@@ -0,0 +1,59 @@
+
+
+
diff --git a/resources/views/components/navbar/user-menu.blade.php b/resources/views/components/navbar/user-menu.blade.php
new file mode 100644
index 0000000..f009d54
--- /dev/null
+++ b/resources/views/components/navbar/user-menu.blade.php
@@ -0,0 +1,25 @@
+@php
+ $name = Illuminate\Support\Facades\Auth::user()->name;
+@endphp
+
diff --git a/resources/views/components/notification/index.blade.php b/resources/views/components/notification/index.blade.php
new file mode 100644
index 0000000..b4c6c3c
--- /dev/null
+++ b/resources/views/components/notification/index.blade.php
@@ -0,0 +1,42 @@
+@php
+ $notificationData = new stdClass();
+ // Ensure $notificationData has 'notifications' as an array to avoid errors in the @forelse directive
+ $notificationData->notifications = $notificationData->notifications ?? [];
+@endphp
+
+
+
+
+
+ {{ $notificationData->count ?? 0 }}
+
+
+
diff --git a/resources/views/components/right-sidebar/index.blade.php b/resources/views/components/right-sidebar/index.blade.php
new file mode 100644
index 0000000..838da54
--- /dev/null
+++ b/resources/views/components/right-sidebar/index.blade.php
@@ -0,0 +1,6 @@
+
diff --git a/resources/views/components/sidebar/header.blade.php b/resources/views/components/sidebar/header.blade.php
new file mode 100644
index 0000000..cf641d3
--- /dev/null
+++ b/resources/views/components/sidebar/header.blade.php
@@ -0,0 +1,5 @@
+
diff --git a/resources/views/components/sidebar/index.blade.php b/resources/views/components/sidebar/index.blade.php
new file mode 100644
index 0000000..704e576
--- /dev/null
+++ b/resources/views/components/sidebar/index.blade.php
@@ -0,0 +1,30 @@
+@php
+ use CodeCoz\AimAdmin\MenuBuilder\AimAdminMenu;
+ $applicationName = env('APP_NAME', 'Aim Admin');
+ $menus = app(AimAdminMenu::class)->menu('sidebar');
+@endphp
+
+
diff --git a/resources/views/components/sidebar/item.blade.php b/resources/views/components/sidebar/item.blade.php
new file mode 100644
index 0000000..f6817ed
--- /dev/null
+++ b/resources/views/components/sidebar/item.blade.php
@@ -0,0 +1,20 @@
+@props(['item'])
+
+@inject('sidebarItemHelper', 'CodeCoz\AimAdmin\MenuBuilder\SidebarItemHelper')
+
+@if ($sidebarItemHelper->isHeader($item))
+
+ {{-- Header --}}
+
+
+@elseif ($sidebarItemHelper->isSubmenu($item))
+
+ {{-- Treeview menu --}}
+
+
+@elseif ($sidebarItemHelper->isLink($item))
+
+ {{-- Link --}}
+
+
+@endif
diff --git a/resources/views/components/sidebar/link.blade.php b/resources/views/components/sidebar/link.blade.php
new file mode 100644
index 0000000..6809480
--- /dev/null
+++ b/resources/views/components/sidebar/link.blade.php
@@ -0,0 +1,20 @@
+@props(['item'])
+
+
+
+
+
+ {{ $item['text'] }}
+
+ @isset($item['label'])
+
+ {{ $item['label'] }}
+
+ @endisset
+
+
+
diff --git a/resources/views/components/sidebar/treeview.blade.php b/resources/views/components/sidebar/treeview.blade.php
new file mode 100644
index 0000000..fd2fc2e
--- /dev/null
+++ b/resources/views/components/sidebar/treeview.blade.php
@@ -0,0 +1,33 @@
+@props(['item'])
+
+
diff --git a/resources/views/components/utils/error.blade.php b/resources/views/components/utils/error.blade.php
new file mode 100644
index 0000000..a0cb24c
--- /dev/null
+++ b/resources/views/components/utils/error.blade.php
@@ -0,0 +1,24 @@
+@props(['messages','type' => 'danger'])
+@if(session('error'))
+ @php
+ $messages = session('error');
+ @endphp
+@elseif ($errors->any() && $errors->get('error'))
+ @php
+ $messages = $errors->get('error');
+ @endphp
+@endif
+@if($messages)
+ @php
+ $messages = is_array($messages) ? $messages : [$messages];
+ @endphp
+
+
+
Alert!
+ @foreach ($messages as $message)
+ @if(is_string($message))
+ {{ $message }}
+ @endif
+ @endforeach
+
+@endif
diff --git a/resources/views/components/utils/input-error.blade.php b/resources/views/components/utils/input-error.blade.php
new file mode 100644
index 0000000..98fa586
--- /dev/null
+++ b/resources/views/components/utils/input-error.blade.php
@@ -0,0 +1,6 @@
+@props(['messages'])
+@if ($messages)
+ @foreach ((array) $messages as $message)
+
{{ $message }}
+ @endforeach
+@endif
diff --git a/resources/views/components/utils/input-errors.blade.php b/resources/views/components/utils/input-errors.blade.php
new file mode 100644
index 0000000..8737884
--- /dev/null
+++ b/resources/views/components/utils/input-errors.blade.php
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/resources/views/components/utils/input-label.blade.php b/resources/views/components/utils/input-label.blade.php
new file mode 100644
index 0000000..bfd2cbb
--- /dev/null
+++ b/resources/views/components/utils/input-label.blade.php
@@ -0,0 +1,5 @@
+@props(['value'])
+
+
diff --git a/resources/views/components/utils/input.blade.php b/resources/views/components/utils/input.blade.php
new file mode 100644
index 0000000..ea370d4
--- /dev/null
+++ b/resources/views/components/utils/input.blade.php
@@ -0,0 +1,3 @@
+@props(['disabled' => false])
+
+
merge(['class' => 'form-control']) !!}>
diff --git a/resources/views/components/utils/modal.blade.php b/resources/views/components/utils/modal.blade.php
new file mode 100644
index 0000000..c3d4e01
--- /dev/null
+++ b/resources/views/components/utils/modal.blade.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ Loading
+
+
+
+
+
diff --git a/resources/views/components/utils/primary-button.blade.php b/resources/views/components/utils/primary-button.blade.php
new file mode 100644
index 0000000..c492478
--- /dev/null
+++ b/resources/views/components/utils/primary-button.blade.php
@@ -0,0 +1,3 @@
+
diff --git a/resources/views/components/utils/title.blade.php b/resources/views/components/utils/title.blade.php
new file mode 100644
index 0000000..00060bb
--- /dev/null
+++ b/resources/views/components/utils/title.blade.php
@@ -0,0 +1,3 @@
+
+
{{$slot}}
+
diff --git a/resources/views/create.blade.php b/resources/views/create.blade.php
new file mode 100644
index 0000000..7667eeb
--- /dev/null
+++ b/resources/views/create.blade.php
@@ -0,0 +1,6 @@
+
+
+ {{ $pageTitle ?? 'Create Item' }}
+
+
+
diff --git a/resources/views/crudboard/actions/board-action.blade.php b/resources/views/crudboard/actions/board-action.blade.php
new file mode 100644
index 0000000..162d390
--- /dev/null
+++ b/resources/views/crudboard/actions/board-action.blade.php
@@ -0,0 +1,8 @@
+@php $routeParams = $crudAction->getRouteParameters(); @endphp
+@if($crudAction->shouldBeDisplayedFor(null))
+ @if($crudAction->isButton())
+
+ @else
+
+ @endif
+@endif
diff --git a/resources/views/crudboard/actions/board-button.blade.php b/resources/views/crudboard/actions/board-button.blade.php
new file mode 100644
index 0000000..5493033
--- /dev/null
+++ b/resources/views/crudboard/actions/board-button.blade.php
@@ -0,0 +1,7 @@
+
+ @if ($crudAction->getIcon())
+
+ @endif
+ {{ $crudAction->getLabel() }}
+
\ No newline at end of file
diff --git a/resources/views/crudboard/actions/btn.blade.php b/resources/views/crudboard/actions/btn.blade.php
new file mode 100644
index 0000000..3645a05
--- /dev/null
+++ b/resources/views/crudboard/actions/btn.blade.php
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/resources/views/crudboard/actions/button.blade.php b/resources/views/crudboard/actions/button.blade.php
new file mode 100644
index 0000000..fbe82a1
--- /dev/null
+++ b/resources/views/crudboard/actions/button.blade.php
@@ -0,0 +1,5 @@
+@if($action->isSubmitAction())
+
+@else
+
{{$action->getLabel() }}
+@endif
\ No newline at end of file
diff --git a/resources/views/crudboard/actions/filter-action.blade.php b/resources/views/crudboard/actions/filter-action.blade.php
new file mode 100644
index 0000000..fc83fd3
--- /dev/null
+++ b/resources/views/crudboard/actions/filter-action.blade.php
@@ -0,0 +1,6 @@
+@php $routeParams = $action->getRouteParameters(); @endphp
+@if($action->isButton())
+
+@else
+
+@endif
diff --git a/resources/views/crudboard/actions/form-action.blade.php b/resources/views/crudboard/actions/form-action.blade.php
new file mode 100644
index 0000000..fc83fd3
--- /dev/null
+++ b/resources/views/crudboard/actions/form-action.blade.php
@@ -0,0 +1,6 @@
+@php $routeParams = $action->getRouteParameters(); @endphp
+@if($action->isButton())
+
+@else
+
+@endif
diff --git a/resources/views/crudboard/actions/grid-button.blade.php b/resources/views/crudboard/actions/grid-button.blade.php
new file mode 100644
index 0000000..d2246a5
--- /dev/null
+++ b/resources/views/crudboard/actions/grid-button.blade.php
@@ -0,0 +1,7 @@
+
+ @if($rowAction->isButton())
+
+ @else
+
+ @endif
+
diff --git a/resources/views/crudboard/actions/grid-delete-button.blade.php b/resources/views/crudboard/actions/grid-delete-button.blade.php
new file mode 100644
index 0000000..06e28f9
--- /dev/null
+++ b/resources/views/crudboard/actions/grid-delete-button.blade.php
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/resources/views/crudboard/actions/group.blade.php b/resources/views/crudboard/actions/group.blade.php
new file mode 100644
index 0000000..f608aba
--- /dev/null
+++ b/resources/views/crudboard/actions/group.blade.php
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/crudboard/actions/link.blade.php b/resources/views/crudboard/actions/link.blade.php
new file mode 100644
index 0000000..8ea2e18
--- /dev/null
+++ b/resources/views/crudboard/actions/link.blade.php
@@ -0,0 +1,16 @@
+
getRouteName())
+ href="{{ route($action->getRouteName(), $routeParams) }}"
+ @elseif($action->getUrl())
+ href="{{$action->getUrl()}}"
+ @else
+ href="#"
+ @endif
+ {!! $htmlAttributes !!}>
+ @if ($action->getIcon())
+
+ @endif
+ {{ $action->getLabel() }}
+
+
+
diff --git a/resources/views/crudboard/actions/show-button.blade.php b/resources/views/crudboard/actions/show-button.blade.php
new file mode 100644
index 0000000..0f1a75b
--- /dev/null
+++ b/resources/views/crudboard/actions/show-button.blade.php
@@ -0,0 +1,9 @@
+@php
+ $routeParams = $action->getRouteParameters();
+ $htmlAttributes = $action->getAttributesAsHtml();
+@endphp
+@if($action->isButton())
+
+@else
+
+@endif
diff --git a/resources/views/crudboard/fields/chainselect.blade.php b/resources/views/crudboard/fields/chainselect.blade.php
new file mode 100644
index 0000000..93604d4
--- /dev/null
+++ b/resources/views/crudboard/fields/chainselect.blade.php
@@ -0,0 +1,47 @@
+
+ @foreach ( $field->getCustomOption('children') as $key=>$child)
+ @php
+ if($dependant = $child->getCustomOption('dependant')){
+ $child->setHtmlAttribute('onchange',"loadDependant(this,'$dependant')");
+ }
+ $htmlAttributes = $child->getAttributesAsHtml() ;
+ @endphp
+
+
+
+ @endforeach
+
+
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
+
+@pushOnce('scripts')
+
+@endPushOnce
diff --git a/resources/views/crudboard/fields/checkbox.blade.php b/resources/views/crudboard/fields/checkbox.blade.php
new file mode 100644
index 0000000..a90c414
--- /dev/null
+++ b/resources/views/crudboard/fields/checkbox.blade.php
@@ -0,0 +1,24 @@
+
+
+
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
+
+
diff --git a/resources/views/crudboard/fields/choice.blade.php b/resources/views/crudboard/fields/choice.blade.php
new file mode 100644
index 0000000..d7bc5ed
--- /dev/null
+++ b/resources/views/crudboard/fields/choice.blade.php
@@ -0,0 +1,67 @@
+
+@switch($field->getInputType())
+ @case("radio")
+ @case("checkbox")
+
+ @break
+ @default
+
+@endswitch
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
+
diff --git a/resources/views/crudboard/fields/datetime.blade.php b/resources/views/crudboard/fields/datetime.blade.php
new file mode 100644
index 0000000..6a01556
--- /dev/null
+++ b/resources/views/crudboard/fields/datetime.blade.php
@@ -0,0 +1,15 @@
+
+
getAttributesAsHtml() }} class="form-control {{ $field->getCssClass() }}"
+ id="{{ $field->getName() }}" name="{{ $field->getName() }}"
+ type="date" placeholder="{{ $field->getPlaceholder('placeholder')}}"
+ value="{{ old($field->getName(),$field->getFormattedValue()) }}"
+ @if($field->isDisabled()) disabled @endif @if($field->isREadonly()) readonly @endif
+/>
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
+
diff --git a/resources/views/crudboard/fields/file.blade.php b/resources/views/crudboard/fields/file.blade.php
new file mode 100644
index 0000000..916e439
--- /dev/null
+++ b/resources/views/crudboard/fields/file.blade.php
@@ -0,0 +1,11 @@
+
+
isRequired()) {!! $htmlAttributes !!} @disabled($field->isDisabled()) @readonly($field->isReadonly()) />
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
diff --git a/resources/views/crudboard/fields/form-element.blade.php b/resources/views/crudboard/fields/form-element.blade.php
new file mode 100644
index 0000000..a7735e7
--- /dev/null
+++ b/resources/views/crudboard/fields/form-element.blade.php
@@ -0,0 +1,11 @@
+
+@php
+ $attributes = "";
+@endphp
+
+@switch($field->getHtmlElement())
+ @case("input")
+
+ @break;
+
diff --git a/resources/views/crudboard/fields/grid_cell.blade.php b/resources/views/crudboard/fields/grid_cell.blade.php
new file mode 100644
index 0000000..aa91d0f
--- /dev/null
+++ b/resources/views/crudboard/fields/grid_cell.blade.php
@@ -0,0 +1 @@
+{!! $value !!}
\ No newline at end of file
diff --git a/resources/views/crudboard/fields/group.blade.php b/resources/views/crudboard/fields/group.blade.php
new file mode 100644
index 0000000..d4021a2
--- /dev/null
+++ b/resources/views/crudboard/fields/group.blade.php
@@ -0,0 +1,19 @@
+
+
+ @foreach ( $field->getCustomOption('fields') as $key=>$childField)
+ @php
+ $htmlAttributes = $childField->getAttributesAsHtml() ;
+ @endphp
+
+
+
+ @endforeach
+
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
+
diff --git a/resources/views/crudboard/fields/hidden.blade.php b/resources/views/crudboard/fields/hidden.blade.php
new file mode 100644
index 0000000..fb01bd6
--- /dev/null
+++ b/resources/views/crudboard/fields/hidden.blade.php
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/resources/views/crudboard/fields/id.blade.php b/resources/views/crudboard/fields/id.blade.php
new file mode 100644
index 0000000..143f035
--- /dev/null
+++ b/resources/views/crudboard/fields/id.blade.php
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/views/crudboard/fields/input.blade.php b/resources/views/crudboard/fields/input.blade.php
new file mode 100644
index 0000000..6b4cbb4
--- /dev/null
+++ b/resources/views/crudboard/fields/input.blade.php
@@ -0,0 +1,15 @@
+
+
isDisabled()) disabled @endif @if($field->isReadonly()) readonly
+ @endif @if($field->isRequired()) required @endif
+/>
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
+
diff --git a/resources/views/crudboard/fields/textarea.blade.php b/resources/views/crudboard/fields/textarea.blade.php
new file mode 100644
index 0000000..d9abd4f
--- /dev/null
+++ b/resources/views/crudboard/fields/textarea.blade.php
@@ -0,0 +1,12 @@
+
+
+@if($field->getHelp())
+
{!! $field->getHelp() !!}
+@endif
+
+
diff --git a/resources/views/crudboard/filter.blade.php b/resources/views/crudboard/filter.blade.php
new file mode 100644
index 0000000..052e773
--- /dev/null
+++ b/resources/views/crudboard/filter.blade.php
@@ -0,0 +1,48 @@
+
+
+@push('scripts')
+
+@endpush
diff --git a/resources/views/crudboard/form.blade.php b/resources/views/crudboard/form.blade.php
new file mode 100644
index 0000000..d3d16bf
--- /dev/null
+++ b/resources/views/crudboard/form.blade.php
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+ @if(config('aim-admin.show_inline_alert_box', true))
+
+ @endif
+
+
+
+
+
diff --git a/resources/views/crudboard/grid.blade.php b/resources/views/crudboard/grid.blade.php
new file mode 100644
index 0000000..b8b1b03
--- /dev/null
+++ b/resources/views/crudboard/grid.blade.php
@@ -0,0 +1,115 @@
+
+ @if($grid->getFilter()->getFields()->count() > 0)
+
+ @endif
+
+
+
+ @if(config('aim-admin.show_inline_alert_box', true))
+
+ @endif
+
+
+
+
+ @php
+ $gridData = $grid->getGridData();
+ $perPage = $gridData->perPage()??10;
+ $currentPage = $gridData->currentPage();
+ $indexCount = $perPage*($currentPage-1);
+ @endphp
+ @forelse($gridData as $k=>$row)
+ @php
+ $rowCssClass = $grid->getRowCssClass($row);
+ $rowCss = $grid->getRowCss($row);
+ @endphp
+
+ @if(!$grid->isDisableSerialColumn())
+ {{ $indexCount + $k + 1 }} |
+ @endif
+ @foreach ($grid->getColumns() as $column)
+
+ @php
+ $value = $row[$column->getName()] ;
+ if($formaterFunc = $column->getFormatValueCallable()){
+ $value = $formaterFunc($value,$row);
+ }
+ @endphp
+
+ |
+ @endforeach
+ @if($grid->getRowActions()->count())
+
+ @foreach ($grid->getRowActions() as $rowAction)
+ @php
+ $routeParams = $rowAction->getRouteParameters();
+ if ($routeParams instanceof \Closure) {
+ $routeParams = $routeParams($row);
+ } else {
+ foreach ($routeParams as $key => $val) {
+ $routeParams[$key] = $row[$val];
+ }
+ empty($routeParams) && ($routeParams['id'] = $row['id']);
+ }
+ $htmlAttributes = $rowAction->getAttributesAsHtml();
+ @endphp
+ @if($rowAction->shouldBeDisplayedFor($row))
+
+ @endif
+ @endforeach
+ |
+ @endif
+
+
+ @empty
+
+ getRowActions()->count())
+ colspan="{{ $grid->getColumns()->count() + 2 }}"
+ @else
+ colspan="{{ $grid->getColumns()->count() + 1 }}"
+ @endif
+ class="text-center">No record is
+ found
+ |
+
+ @endforelse
+
+
+
+
+
+
+
+
diff --git a/resources/views/crudboard/pagination.blade.php b/resources/views/crudboard/pagination.blade.php
new file mode 100644
index 0000000..ff5c4c3
--- /dev/null
+++ b/resources/views/crudboard/pagination.blade.php
@@ -0,0 +1,12 @@
+
+
+
+ {{ $data->links() }}
+
+
+
+
+ Showing {{ $data->firstItem() }} to {{ $data->lastItem() }} of {{ $data->total() }} entries
+
+
+
diff --git a/resources/views/crudboard/show.blade.php b/resources/views/crudboard/show.blade.php
new file mode 100644
index 0000000..d9feb1a
--- /dev/null
+++ b/resources/views/crudboard/show.blade.php
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ @foreach($show->getFields() as $field)
+
+ {{$field->getLabel()}} |
+
+ @if($component = $field->getComponent())
+ @php
+ $value = $field->getValue() ;
+ $record = $show->getRecord();
+ @endphp
+
+ @else
+ {{$field->getValue()}}
+ @endif
+ |
+
+ @endforeach
+
+
+ @foreach($show->getActions()->getShowActions() as $action)
+ @if($action->shouldBeDisplayedFor($show->getRecord()))
+ @php $htmlActionAttributes = $action->getAttributesAsHtml() ; @endphp
+
+ @endif
+ @endforeach
+
+
+
diff --git a/resources/views/crudboard/toast.blade.php b/resources/views/crudboard/toast.blade.php
new file mode 100644
index 0000000..b9ee30a
--- /dev/null
+++ b/resources/views/crudboard/toast.blade.php
@@ -0,0 +1,38 @@
+
+
+{{-- Toast.fire({
+ icon: 'success',
+ title: 'Success'
+ })
+ Toast.fire({
+ icon: 'error',
+ title: 'Error'
+ })
+ Toast.fire({
+ icon: 'warning',
+ title: 'Warning'
+ })
+ Toast.fire({
+ icon: 'info',
+ title: 'Info'
+ })
+ Toast.fire({
+ icon: 'question',
+ title: 'Question'
+ }) --}}
diff --git a/resources/views/edit.blade.php b/resources/views/edit.blade.php
new file mode 100644
index 0000000..0fd46b0
--- /dev/null
+++ b/resources/views/edit.blade.php
@@ -0,0 +1,7 @@
+
+
+ {{ $pageTitle ?? 'Edit Item' }}
+
+
+
+
diff --git a/resources/views/list.blade.php b/resources/views/list.blade.php
new file mode 100644
index 0000000..18eca78
--- /dev/null
+++ b/resources/views/list.blade.php
@@ -0,0 +1,7 @@
+
+
+ {{ $pageTitle ?? 'Item List' }}
+
+
+
+
diff --git a/resources/views/show.blade.php b/resources/views/show.blade.php
new file mode 100644
index 0000000..c89dbd9
--- /dev/null
+++ b/resources/views/show.blade.php
@@ -0,0 +1,7 @@
+
+
+ {{ $pageTitle ?? 'Item Show' }}
+
+
+
+
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 0000000..d2c13e7
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,19 @@
+ config('aim-admin.middleware', ['guest', 'web'])], function () {
+ Route::get(config('aim-admin.auth.url', 'login'), [config('aim-admin.auth.controller', LoginController::class), 'login'])->name('login');
+ Route::post(config('aim-admin.auth.url', 'login'), [config('aim-admin.auth.controller', LoginController::class), 'authenticate']);
+
+ Route::get('forget-password', [LoginController::class, 'passwordReset'])->name('forget.password');
+ Route::get('password/reset/{token}', [LoginController::class, 'passwordReset'])->name('password.reset');
+ Route::post('password/reset', [LoginController::class, 'reset'])->name('password.reset.update');
+
+ Route::get('registration', [config('aim-admin.registration.controller', RegistrationController::class), 'registration'])->name('registration');
+ Route::post('registration', [config('aim-admin.registration.controller', RegistrationController::class), 'register']);
+ Route::get(config('aim-admin.auth.logout_url', 'logout'), [config('aim-admin.auth.controller', LoginController::class), 'logout'])
+ ->name('logout');
+});
diff --git a/src/Admin.php b/src/Admin.php
new file mode 100644
index 0000000..8d36f6c
--- /dev/null
+++ b/src/Admin.php
@@ -0,0 +1,14 @@
+viewsPath = Admin::packagePath('resources/views');
+ $this->configPath = Admin::packagePath('config/aim-admin.php');
+ $this->routePath = Admin::packagePath('routes/web.php');
+ $this->migrationPath = Admin::packagePath('database/migrations');
+
+ // Internal Interfaces Bindings.
+ $this->interfacesBindings();
+
+ // Register user service provider.
+// $this->app->register(UserServiceProvider::class);
+
+ // Register the Menu service provider.
+ $this->app->register(MenuServiceProvider::class);
+
+ // Register the Repo service provider.
+ $this->app->register(RepositoryServiceProvider::class);
+
+ // Register View Service Provider for inject Process
+ $this->app->register(ViewServiceProvider::class);
+
+ // cd pa$this->callAfterResolving('blade.compiler', fn(BladeCompiler $bladeCompiler) => $this->registerBladeExtensions($bladeCompiler));
+
+ }
+
+ protected function registerBladeExtensions($bladeCompiler): void
+ {
+ $bladeMethodWrapper = '\\CodeCoz\\AimAdmin\\AdminServiceProvider::bladeMethodWrapper';
+
+ $bladeCompiler->directive('hasanypermission', fn($args) => "");
+ $bladeCompiler->directive('elsehasanypermission', fn($args) => "");
+ $bladeCompiler->directive('endhasanypermission', fn() => '');
+
+ $bladeCompiler->directive('hasrole', fn($args) => "");
+ $bladeCompiler->directive('endhasrole', fn() => '');
+
+ $bladeCompiler->directive('hasanyrole', fn($args) => "");
+ $bladeCompiler->directive('endhasanyrole', fn() => '');
+
+ }
+
+ public static function bladeMethodWrapper($method, $role, $guard = null): bool
+ {
+ return true;
+ }
+
+ /**
+ * Interfaces bindings
+ *
+ * @return void
+ */
+ private function interfacesBindings(): void
+ {
+ // Register the service the package provides.
+ $this->app->singleton($this->packagePrefix, function ($app) {
+ return new Admin;
+ });
+
+ // Register the extra Interfaces to the package provides.
+ $this->app->singleton(CrudBoardInterface::class, CrudBoard::class);
+
+ }
+
+ /**
+ * Perform post-registration booting of services.
+ *
+ * @param Factory $view
+ * @return void
+ */
+ public function boot(Factory $view): void
+ {
+ $this->loadConfig();
+ $this->loadViews();
+ $this->loadRoutes();
+ $this->loadMigration();
+ $this->loadBladeComponentNamespace();
+ $this->registerCommands();
+ $this->registerGlobalMiddleware();
+ Paginator::useBootstrapFour();
+
+ $this->publishMigrations();
+
+ }
+
+ /**
+ * Load the Blade Component Namespace.
+ *
+ * @return void
+ */
+ private function loadBladeComponentNamespace(): void
+ {
+ Blade::componentNamespace('CodeCoz\\AimAdmin\View\\Components', $this->packagePrefix);
+ Blade::anonymousComponentPath($this->viewsPath, $this->packagePrefix);
+ }
+
+
+ /**
+ * Load the package views.
+ *
+ * @return void
+ */
+ private function loadViews(): void
+ {
+ // Use the default package views path
+ $this->loadViewsFrom($this->viewsPath, $this->packagePrefix);
+ }
+
+ /**
+ * Publish the migrations.
+ *
+ * @return void
+ */
+ private function publishMigrations(): void
+ {
+
+ $this->publishes([$this->migrationPath => database_path('migrations'),
+ ], 'migrations');
+
+ }
+
+ /**
+ * Load the package config.
+ *
+ * @return void
+ */
+ private function loadConfig(): void
+ {
+ $this->mergeConfigFrom($this->configPath, $this->packagePrefix);
+ }
+
+ /**
+ * Load the package routes.
+ * @return void
+ */
+ private function loadRoutes(): void
+ {
+ $this->loadRoutesFrom($this->routePath);
+ }
+
+ /**
+ * Load the package migration.
+ * @return void
+ */
+ private function loadMigration(): void
+ {
+ $this->loadMigrationsFrom($this->migrationPath);
+ }
+
+
+ /**
+ * Register the package's Global Middleware.
+ *
+ * @return void
+ */
+ private function registerGlobalMiddleware(): void
+ {
+// $this->app['router']->aliasMiddleware('role', RoleMiddleware::class);
+// $this->app['router']->aliasMiddleware('acl', CheckPermission::class);
+// $this->app['router']->pushMiddlewareToGroup('web', ForcePasswordChange::class);
+ }
+
+ /**
+ * Register the package's artisan commands.
+ *
+ * @return void
+ */
+ private function registerCommands(): void
+ {
+ $this->commands([
+ InstallAimAdminCommand::class,
+ UpgradeAimAdminCommand::class,
+ MakeAimController::class,
+ MakeAimRepositoryInterface::class,
+ MakeAimServiceInterface::class,
+ MakeAimRepository::class,
+ MakeAimService::class,
+ AimAdminCommand::class,
+ AddToRouteCommand::class,
+ ]);
+ }
+
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides()
+ {
+ return [$this->packagePrefix];
+ }
+
+}
diff --git a/src/Collection/AbstractCollection.php b/src/Collection/AbstractCollection.php
new file mode 100644
index 0000000..6ec71c8
--- /dev/null
+++ b/src/Collection/AbstractCollection.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Collection;
+
+
+/**
+ * This class is a collection abstract class abstract.
+ *
+ * @author CodeCoz
+ */
+abstract class AbstractCollection implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+ protected array $items;
+
+ abstract function offsetGet(mixed $offset): mixed;
+
+ public function isEmpty(): bool
+ {
+ return 0 === \count($this->items);
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return \array_key_exists($offset, $this->items);
+ }
+
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ $this->items[$offset] = $value;
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ unset($this->items[$offset]);
+ }
+
+ public function getIterator(): \ArrayIterator
+ {
+ return new \ArrayIterator($this->items);
+ }
+
+ public function count(): int
+ {
+ return \count($this->items);
+ }
+
+ public function all(): array
+ {
+ return $this->items;
+ }
+
+}
diff --git a/src/Collection/ActionCollection.php b/src/Collection/ActionCollection.php
new file mode 100644
index 0000000..afff8f0
--- /dev/null
+++ b/src/Collection/ActionCollection.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Collection;
+
+use CodeCoz\AimAdmin\Dto\CrudBoard\ActionDto;
+
+
+/**
+ * This class is for action collection in crudboard .
+ *
+ * @author CodeCoz
+ */
+final class ActionCollection extends AbstractCollection
+{
+
+ private function __construct(array $actions)
+ {
+ $this->items = $actions;
+ }
+
+ public function __clone()
+ {
+ foreach ($this->items as $actionName => $actionDto) {
+ $this->items[$actionName] = clone $actionDto;
+ }
+ }
+
+ /**
+ * @param ActionDto[] $actions
+ */
+ public static function init(array $actions): self
+ {
+ return new self($actions);
+ }
+
+ public function get(string $actionName): ?ActionDto
+ {
+ return $this->items[$actionName] ?? null;
+ }
+
+ public function offsetExists(mixed $offset): bool
+ {
+ return \array_key_exists($offset, $this->items);
+ }
+
+ public function offsetGet(mixed $offset): ActionDto
+ {
+ return $this->items[$offset];
+ }
+
+ public function getRowActions(): self
+ {
+ return self::init(array_filter(
+ $this->items,
+ static fn(ActionDto $action): bool => $action->isRowAction()
+ ));
+ }
+
+ public function getCrudBoardActions(): self
+ {
+ return self::init(array_filter(
+ $this->items,
+ static fn(ActionDto $action): bool => $action->isCrudBoardAction()
+ ));
+ }
+
+ public function getBatchActions(): self
+ {
+ return self::init(array_filter(
+ $this->items,
+ static fn(ActionDto $action): bool => $action->isBatchAction()
+ ));
+ }
+
+ public function getFormActions(): self
+ {
+ return self::init(array_filter(
+ $this->items,
+ static fn(ActionDto $action): bool => $action->isFormAction()
+ ));
+ }
+
+ public function getShowActions(): self
+ {
+ return self::init(array_filter(
+ $this->items,
+ static fn(ActionDto $action): bool => $action->isShowAction()
+ ));
+ }
+
+ public function getFilterActions(): static
+ {
+ return self::init(array_filter(
+ $this->items,
+ static fn(ActionDto $action): bool => $action->isFilterAction()
+ ));
+ }
+
+}
diff --git a/src/Collection/FieldCollection.php b/src/Collection/FieldCollection.php
new file mode 100644
index 0000000..ddf8ffd
--- /dev/null
+++ b/src/Collection/FieldCollection.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Collection;
+
+use CodeCoz\AimAdmin\Dto\CrudBoard\FieldDto;
+use CodeCoz\AimAdmin\Field\Field;
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author CodeCoz
+ */
+final class FieldCollection extends AbstractCollection
+{
+
+ private function __construct(iterable $fields)
+ {
+ $this->items = $this->processFields($fields);
+ }
+
+ public static function init(iterable $fields): self
+ {
+ return new self($fields);
+ }
+
+ public function offsetGet(mixed $offset): FieldDto
+ {
+ return $this->items[$offset];
+ }
+
+ private function processFields(iterable $fields): array
+ {
+ $dtos = [];
+ foreach ($fields as $field) {
+ if (\is_string($field)) {
+ $field = Field::init($field, $field);
+ }
+
+ $dto = $field->getDto();
+ $dtos[$dto->getName()] = $dto;
+ }
+
+ return $dtos;
+ }
+
+ public function prepend(FieldDto $field): void
+ {
+ $this->items = \array_unshift($this->items, $field);
+ }
+
+ public function first(): ?FieldDto
+ {
+ if (0 === \count($this->items)) {
+ return null;
+ }
+
+ return $this->items[array_key_first($this->items)];
+ }
+
+ public function get(string $name): ?FieldDto
+ {
+ return $this->items[$name] ?? null;
+ }
+
+ public function set(FieldDto $field): void
+ {
+ $this->items[$field->getName()] = $field;
+ }
+
+ public function unset(FieldDto $field): void
+ {
+ unset($this->items[$field->getName()]);
+ }
+
+ public function add($field)
+ {
+ if (\is_string($field)) {
+ $field = Field::init($field, $field);
+ }
+ $dto = $field->getDto();
+ $this->set($dto);
+ }
+
+
+}
diff --git a/src/Collection/FormFieldCollection.php b/src/Collection/FormFieldCollection.php
new file mode 100644
index 0000000..acf22ec
--- /dev/null
+++ b/src/Collection/FormFieldCollection.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Collection;
+
+use CodeCoz\AimAdmin\Dto\CrudBoard\FormFieldDto;
+use CodeCoz\AimAdmin\Field\Field;
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author CodeCoz
+ */
+final class FormFieldCollection extends AbstractCollection
+{
+
+ private function __construct(iterable $fields)
+ {
+ $this->items = $this->processFields($fields);
+ }
+
+ public static function init(iterable $fields): self
+ {
+ return new self($fields);
+ }
+
+ public function offsetGet(mixed $offset): FormFieldDto
+ {
+ return $this->items[$offset];
+ }
+
+ private function processFields(iterable $fields): array
+ {
+ $dtos = [];
+ foreach ($fields as $field) {
+ if (\is_string($field)) {
+ $field = Field::init($field, $field);
+ }
+
+ $dto = $field->getDto();
+ $dtos[$dto->getName()] = $dto;
+ }
+
+ return $dtos;
+ }
+
+ public function prepend(FormFieldDto $field): void
+ {
+ $this->items = \array_unshift($this->items, $field);
+ }
+
+ public function first(): ?FormFieldDto
+ {
+ if (0 === \count($this->items)) {
+ return null;
+ }
+
+ return $this->items[array_key_first($this->items)];
+ }
+
+ public function get(string $name): ?FormFieldDto
+ {
+ return $this->items[$name] ?? null;
+ }
+
+ public function set(FormFieldDto $field): void
+ {
+ $this->items[$field->getName()] = $field;
+ }
+
+ public function unset(FormFieldDto $field): void
+ {
+ unset($this->items[$field->getName()]);
+ }
+
+ public function add($field)
+ {
+ if (\is_string($field)) {
+ $field = Field::init($field, $field);
+ }
+ $dto = $field->getDto();
+ $this->set($dto);
+ }
+
+ public function hasFileInput(): bool
+ {
+ $result = false;
+ foreach ($this->items as $FieldDto) {
+ if ($result = $FieldDto->isFileInput()) {
+ break;
+ }
+ }
+ return $result;
+ }
+
+}
diff --git a/src/Console/Commands/AddToRouteCommand.php b/src/Console/Commands/AddToRouteCommand.php
new file mode 100644
index 0000000..54ec102
--- /dev/null
+++ b/src/Console/Commands/AddToRouteCommand.php
@@ -0,0 +1,56 @@
+argument('name'));
+ $controllerName = ucfirst($name) . 'Controller';
+
+ $routes = << '{$name}', 'middleware' => ['web', 'auth']], function () {
+ Route::get('/', [{$controllerName}::class, 'list'])->name('{$name}.list');
+ Route::get('/show/{id}', [{$controllerName}::class, 'show'])->name('{$name}.show');
+ Route::get('/edit/{id}', [{$controllerName}::class, 'edit'])->name('{$name}.edit');
+ Route::post('/update', [{$controllerName}::class, 'update'])->name('{$name}.update');
+ Route::post('/delete/{id}', [{$controllerName}::class, 'delete'])->name('{$name}.delete');
+ Route::get('/create', [{$controllerName}::class, 'create'])->name('{$name}.create');
+ Route::post('/create', [{$controllerName}::class, 'store'])->name('{$name}.store');
+ });
+ EOT;
+ $routeFile = base_path('routes/web.php');
+ $currentRoutes = File::get($routeFile);
+ if (!str_contains($currentRoutes, $routes)) {
+ if (File::append($routeFile, "\n" . $routes)) {
+ $this->info(ucfirst($name) . ' routes have been added successfully.');
+ } else {
+ $this->error('Failed to add ' . $name . ' routes.');
+ }
+ } else {
+ $this->info('The routes for ' . ucfirst($name) . ' already exist in the routes file.');
+ }
+ }
+
+}
diff --git a/src/Console/Commands/AimAdminCommand.php b/src/Console/Commands/AimAdminCommand.php
new file mode 100644
index 0000000..dc8e6a2
--- /dev/null
+++ b/src/Console/Commands/AimAdminCommand.php
@@ -0,0 +1,126 @@
+choice(
+ 'What do you want to create ?',
+ [self::TYPE_CONTROLLER, self::TYPE_REPOSITORY, self::TYPE_SERVICE, self::TYPE_Request, self::TYPE_ROUTE, self::TYPE_ALL],
+ 5,
+ $maxAttempts = null,
+ $allowMultipleSelections = true
+ );
+
+ $name = $this->ask("What is the name ?", 'Product');
+
+ foreach ($types as $type) {
+ switch ($type) {
+ case self::TYPE_CONTROLLER:
+ $this->createController($name);
+ break;
+
+ case self::TYPE_REPOSITORY:
+ $this->createRepository($name);
+ break;
+
+ case self::TYPE_SERVICE:
+ $this->createService($name);
+ break;
+
+ case self::TYPE_Request:
+ $this->createRequest($name);
+ break;
+
+ case self::TYPE_ALL:
+ $this->createAll($name);
+ break;
+
+ case self::TYPE_ROUTE:
+ $this->addToRoute($name);
+ break;
+ }
+ }
+
+ }
+
+ protected function createController($name): void
+ {
+ $this->call('aim-admin:make-controller', ['name' => $name]);
+ }
+
+ protected function createRepository($name): void
+ {
+ $this->call('aim-admin:make-repo-interface', ['name' => $name]);
+ $this->call('aim-admin:make-repo', ['name' => $name]);
+ }
+
+ protected function createService($name): void
+ {
+ $this->call('aim-admin:make-service-interface', ['name' => $name]);
+ $this->call('aim-admin:make-service', ['name' => $name]);
+ }
+
+ protected function createRequest($name): void
+ {
+ Artisan::call("make:request", ['name' => $name . 'Request']);
+ }
+
+ protected function addToRoute($name): void
+ {
+ $this->call('aim-admin:add-to-route', ['name' => $name]);
+ }
+
+ protected function createAll($name)
+ {
+ $this->createController($name);
+ $this->createRepository($name);
+ $this->createService($name);
+ $this->createRequest($name);
+ $this->addToRoute($name);
+ }
+
+ /**
+ * Get the console command arguments.
+ *
+ * @return array
+ */
+ protected function getArguments()
+ {
+ return [
+ ['name', InputArgument::REQUIRED, 'The name of the model to which the repository will be generated'],
+ ];
+ }
+}
diff --git a/src/Console/Commands/MakeAimController.php b/src/Console/Commands/MakeAimController.php
new file mode 100644
index 0000000..7311610
--- /dev/null
+++ b/src/Console/Commands/MakeAimController.php
@@ -0,0 +1,124 @@
+resolveStubPath('/stubs/aim-admin.controller.stub');
+ }
+
+ /**
+ * Resolve the fully-qualified path to the stub.
+ *
+ * @param string $stub
+ * @return string
+ */
+ protected function resolveStubPath($stub): string
+ {
+ return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
+ ? $customPath
+ : __DIR__ . $stub;
+ }
+
+ /**
+ * Build the class with the given name.
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function buildClass($name): string
+ {
+ $stub = parent::buildClass($name);
+
+ $fullName = Str::singular(class_basename($name));
+
+ $cleanedName = str_replace("Controller", "", $fullName);
+
+ $camelModel = Str::camel($cleanedName);
+
+ // Replace placeholders in one go
+ return str_replace(
+ ["{{ camelName }}", "{{ cleanName }}"],
+ [$camelModel, $cleanedName],
+ $stub
+ );
+ }
+
+ /**
+ * Get the default namespace for the class.
+ *
+ * @param string $rootNamespace
+ * @return string
+ */
+ protected function getDefaultNamespace($rootNamespace): string
+ {
+ return $rootNamespace . '\Http\Controllers';
+ }
+
+ protected function getNameInput(): string
+ {
+ $name = trim($this->argument('name'));
+
+ // Append 'Controller' to the name if it doesn't already end with it
+ if (!str_ends_with($name, 'Controller')) {
+ $name .= 'Controller';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions(): array
+ {
+ return [
+ ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the class already exists'],
+ ];
+ }
+}
diff --git a/src/Console/Commands/MakeAimRepository.php b/src/Console/Commands/MakeAimRepository.php
new file mode 100644
index 0000000..b0573a1
--- /dev/null
+++ b/src/Console/Commands/MakeAimRepository.php
@@ -0,0 +1,135 @@
+resolveStubPath('/stubs/aim-admin.repository.stub');
+ }
+
+ /**
+ * Build the class with the given name.
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function buildClass($name): string
+ {
+ // Extract model name and namespace from input name
+ $parts = explode('\\', trim($this->argument('name')));
+ $model = Str::singular(end($parts));
+ $namespace = implode('\\', array_slice($parts, 0, -1));
+
+ // Construct repository namespace
+ $repoContractsNamespace = "App\Contracts\Repositories";
+ if (!empty($namespace)) {
+ $repoContractsNamespace .= "\\" . $namespace;
+ }
+
+ // Replace placeholders and return stub
+ return str_replace(
+ [
+ "{{ tableModel }}",
+ "{{ repoContractsNamespace }}"
+ ],
+ [
+ $model,
+ $repoContractsNamespace
+ ],
+ parent::buildClass($name)
+ );
+ }
+
+
+ /**
+ * Resolve the fully-qualified path to the stub.
+ *
+ * @param string $stub
+ * @return string
+ */
+ protected function resolveStubPath($stub): string
+ {
+ return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
+ ? $customPath
+ : __DIR__ . $stub;
+ }
+
+ /**
+ * Get the default namespace for the class.
+ *
+ * @param string $rootNamespace
+ * @return string
+ */
+ protected function getDefaultNamespace($rootNamespace): string
+ {
+ return $rootNamespace . '\Repositories';
+ }
+
+
+ protected function getNameInput(): string
+ {
+ $name = trim($this->argument('name'));
+
+ // Append 'Controller' to the name if it doesn't already end with it
+ if (!str_ends_with($name, 'Repository')) {
+ $name .= 'Repository';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions(): array
+ {
+ return [
+ ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the class already exists'],
+ ];
+ }
+}
diff --git a/src/Console/Commands/MakeAimRepositoryInterface.php b/src/Console/Commands/MakeAimRepositoryInterface.php
new file mode 100644
index 0000000..84ba531
--- /dev/null
+++ b/src/Console/Commands/MakeAimRepositoryInterface.php
@@ -0,0 +1,99 @@
+resolveStubPath('/stubs/aim-admin.repository.interface.stub');
+ }
+
+ /**
+ * Resolve the fully-qualified path to the stub.
+ *
+ * @param string $stub
+ * @return string
+ */
+ protected function resolveStubPath($stub): string
+ {
+ return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
+ ? $customPath
+ : __DIR__ . $stub;
+ }
+
+ /**
+ * Get the default namespace for the class.
+ *
+ * @param string $rootNamespace
+ * @return string
+ */
+ protected function getDefaultNamespace($rootNamespace): string
+ {
+ return $rootNamespace . '\Contracts\Repositories';
+ }
+
+ protected function getNameInput(): string
+ {
+ $name = trim($this->argument('name'));
+
+ // Append 'Controller' to the name if it doesn't already end with it
+ if (!str_ends_with($name, 'RepositoryInterface')) {
+ $name .= 'RepositoryInterface';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions(): array
+ {
+ return [
+ ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the class already exists'],
+ ];
+ }
+}
diff --git a/src/Console/Commands/MakeAimService.php b/src/Console/Commands/MakeAimService.php
new file mode 100644
index 0000000..da32c36
--- /dev/null
+++ b/src/Console/Commands/MakeAimService.php
@@ -0,0 +1,143 @@
+resolveStubPath('/stubs/aim-admin.service.stub');
+ }
+
+ /**
+ * Build the class with the given name.
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function buildClass($name): string
+ {
+ // Extract model and namespace from input name
+ $parts = explode('\\', trim($this->argument('name')));
+ $namespace = implode('\\', array_slice($parts, 0, -1));
+ $model = Str::studly(Str::replace("Service", "", end($parts)));
+
+ // Repository namespace
+ $repoNamespace = "App\Repositories";
+ if (!empty($namespace)) {
+ $repoNamespace .= "\\" . $namespace;
+ }
+
+ // Construct repository namespace
+ $repoConstructNamespace = "App\Contracts\Repositories";
+ if (!empty($namespace)) {
+ $repoConstructNamespace .= "\\" . $namespace;
+ }
+
+ $serviceContractsNamespace = "App\Contracts\Services";
+ if (!empty($namespace)) {
+ $serviceContractsNamespace .= "\\" . $namespace;
+ }
+
+ // Combine placeholders and values for replacement
+ $replacements = [
+ "{{ camelName }}" => Str::camel($model),
+ "{{ tableModel }}" => $model,
+ "{{ repoNamespace }}" => $repoNamespace,
+ "{{ repoConstructNamespace }}" => $repoConstructNamespace,
+ "{{ serviceContractsNamespace }}" => $serviceContractsNamespace,
+ ];
+
+ // Replace placeholders in stub and return
+ return str_replace(array_keys($replacements), array_values($replacements), parent::buildClass($name));
+ }
+
+ /**
+ * Resolve the fully-qualified path to the stub.
+ *
+ * @param string $stub
+ * @return string
+ */
+ protected function resolveStubPath($stub): string
+ {
+ return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
+ ? $customPath
+ : __DIR__ . $stub;
+ }
+
+ /**
+ * Get the default namespace for the class.
+ *
+ * @param string $rootNamespace
+ * @return string
+ */
+ protected function getDefaultNamespace($rootNamespace): string
+ {
+ return $rootNamespace . '\Services';
+ }
+
+ protected function getNameInput(): string
+ {
+ $name = trim($this->argument('name'));
+
+ // Append 'Controller' to the name if it doesn't already end with it
+ if (!str_ends_with($name, 'Service')) {
+ $name .= 'Service';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions(): array
+ {
+ return [
+ ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the class already exists'],
+ ];
+ }
+}
diff --git a/src/Console/Commands/MakeAimServiceInterface.php b/src/Console/Commands/MakeAimServiceInterface.php
new file mode 100644
index 0000000..8ae2362
--- /dev/null
+++ b/src/Console/Commands/MakeAimServiceInterface.php
@@ -0,0 +1,100 @@
+resolveStubPath('/stubs/aim-admin.service.interface.stub');
+ }
+
+ /**
+ * Resolve the fully-qualified path to the stub.
+ *
+ * @param string $stub
+ * @return string
+ */
+ protected function resolveStubPath($stub): string
+ {
+ return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
+ ? $customPath
+ : __DIR__ . $stub;
+ }
+
+ /**
+ * Get the default namespace for the class.
+ *
+ * @param string $rootNamespace
+ * @return string
+ */
+ protected function getDefaultNamespace($rootNamespace): string
+ {
+ return $rootNamespace . '\Contracts\Services';
+ }
+
+ protected function getNameInput(): string
+ {
+ $name = trim($this->argument('name'));
+
+ // Append 'Controller' to the name if it doesn't already end with it
+ if (!str_ends_with($name, 'ServiceInterface')) {
+ $name .= 'ServiceInterface';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Get the console command options.
+ *
+ * @return array
+ */
+ protected function getOptions(): array
+ {
+ return [
+ ['force', 'f', InputOption::VALUE_NONE, 'Create the class even if the class already exists'],
+ ];
+ }
+}
diff --git a/src/Console/Commands/stubs/aim-admin.controller.stub b/src/Console/Commands/stubs/aim-admin.controller.stub
new file mode 100644
index 0000000..8368ebe
--- /dev/null
+++ b/src/Console/Commands/stubs/aim-admin.controller.stub
@@ -0,0 +1,92 @@
+{{ camelName }}Service = ${{ camelName }}Service;
+ }
+
+ public function getRepository()
+ {
+ return $this->repo;
+ }
+
+ public function configureActions(): iterable
+ {
+ return [
+
+ ];
+ }
+
+ public function configureForm()
+ {
+ $fields = [
+ IdField::init('id'),
+ // TextareaField::init('my_remarks')
+ ];
+ $this->getForm($fields)
+ ->setName('form_name')
+ ->setMethod('post')
+ ->setActionUrl(route('set_route'));
+ }
+
+ public function create()
+ {
+ $this->initCreate();
+ return view('aim-admin::create');
+ }
+
+ public function store(Request $request): RedirectResponse
+ {
+ $this->initStore($request);
+
+ }
+
+ public function configureFilter(): void
+ {
+ $fields = [
+ TextField::init('id'),
+ // TextField::init('other')
+ ];
+ $this->getFilter($fields);
+ }
+
+ public function list()
+ {
+ $this->initGrid(['id'], pagination: 10);
+ return view('aim-admin::list');
+ }
+
+ public function show($id)
+ {
+ $this->initShow($id, ['id', 'created_at']);
+ return view('aim-admin::show');
+ }
+
+ public function edit($id)
+ {
+ $this->initEdit($id);
+ return view('aim-admin::edit');
+ }
+
+ public function delete($id)
+ {
+ // Set your delete functionality
+ }
+
+}
diff --git a/src/Console/Commands/stubs/aim-admin.repository.interface.stub b/src/Console/Commands/stubs/aim-admin.repository.interface.stub
new file mode 100644
index 0000000..71c212f
--- /dev/null
+++ b/src/Console/Commands/stubs/aim-admin.repository.interface.stub
@@ -0,0 +1,9 @@
+orderBy('created_at', 'desc');
+ }
+
+ public function applyFilterQuery(Builder $query, array $filters): Builder
+ {
+ // $filters_name = isset($filters['name']) ? $filters['name'] : "";
+ // $query->where(DB::raw('lower(name)'), 'like', '%'.strtolower($filters_name).'%');
+
+ return parent::applyFilterQuery($query, $filters);
+ }
+
+}
diff --git a/src/Console/Commands/stubs/aim-admin.request.stub b/src/Console/Commands/stubs/aim-admin.request.stub
new file mode 100644
index 0000000..cebf7cc
--- /dev/null
+++ b/src/Console/Commands/stubs/aim-admin.request.stub
@@ -0,0 +1,28 @@
+|string>
+ */
+ public function rules(): array
+ {
+ return [
+ //
+ ];
+ }
+}
diff --git a/src/Console/Commands/stubs/aim-admin.service.interface.stub b/src/Console/Commands/stubs/aim-admin.service.interface.stub
new file mode 100644
index 0000000..437ac70
--- /dev/null
+++ b/src/Console/Commands/stubs/aim-admin.service.interface.stub
@@ -0,0 +1,9 @@
+error('package.json not found.');
+ return;
+ }
+
+ // File path to the 'adminlte.js'
+ $filePath = base_path('resources/js/app.js');
+
+ // Check if the file exists
+ if (!file_exists($filePath)) {
+ $this->error("app.js File does not exist: {$filePath}");
+ return;
+ }
+
+ self::makeFolders();
+ $this->info('Contracts Folders have been created.');
+// self::replacePackageJson();
+// $this->info('Package.json has been replaced.');
+ self::updatePackages();
+ $this->info('package.json has been updated.');
+ self::updateAimAdminAssets();
+ $this->info('app.js has been updated.');
+ self::exportConfig();
+ $this->info('Aim Admin Config file has been exported.');
+ self::exportMigrations();
+ $this->info('Migrations have been exported.');
+ self::exportModel();
+ $this->info('User model has been exported.');
+ self::addedDashboardRoute();
+ $this->info('Home route has been added.');
+ self::addedDashboardBlade();
+ $this->info('Home blade has been added.');
+ self::exportVite();
+ $this->info('vite.config.js has been exported.');
+ $this->info('Aim Admin has been installed successfully. Please run npm i && npm run build');
+ }
+
+
+ /**
+ * Make the Contracts folders
+ * @return void
+ */
+ protected static function makeFolders(): void
+ {
+ tap(new Filesystem, function ($filesystem) {
+
+ $contractsServicesPath = app_path('Contracts/Services');
+ if (!$filesystem->isDirectory($contractsServicesPath)) {
+ $filesystem->makeDirectory($contractsServicesPath, 0755, true);
+ }
+
+ $contractsRepositoriesPath = app_path('Contracts/Repositories');
+ if (!$filesystem->isDirectory($contractsRepositoriesPath)) {
+ $filesystem->makeDirectory($contractsRepositoriesPath, 0755, true);
+ }
+
+ $repositoriesPath = app_path('Repositories');
+ if (!$filesystem->isDirectory($repositoriesPath)) {
+ $filesystem->makeDirectory($repositoriesPath, 0755, true);
+ }
+
+ $servicesPath = app_path('Services');
+ if (!$filesystem->isDirectory($servicesPath)) {
+ $filesystem->makeDirectory($servicesPath, 0755, true);
+ }
+
+ });
+
+ }
+
+ /**
+ * @param array $packages
+ * @param array $requiredPackages
+ * @return array
+ */
+ protected static function updatePackageArray(array $packages, array $requiredPackages): array
+ {
+ foreach ($requiredPackages as $package => $version) {
+ if (!array_key_exists($package, $packages)) {
+ $packages[$package] = $version;
+ }
+ }
+
+ return $packages;
+ }
+
+ /**
+ * Replace the package.json in case of using webpack
+ * @return void
+ */
+ protected static function replacePackageJson(): void
+ {
+ $filesystem = new Filesystem;
+
+ $destinationPath = base_path('package.json');
+ if ($filesystem->exists($destinationPath)) {
+ $filesystem->delete($destinationPath);
+ }
+
+ $sourcePath = __DIR__ . '../../stubs/package.json';
+ $filesystem->copy($sourcePath, $destinationPath);
+ }
+
+ /**
+ * Merge package.json dependency
+ * @return void
+ */
+ protected static function updatePackages(): void
+ {
+ $packagesFile = json_decode(file_get_contents(base_path('package.json')), true);
+
+ $requiredPackages = Helper::requiredPackages();
+
+ // Combine existing dependencies and devDependencies
+ $existingPackages = array_merge(
+ $packagesFile['dependencies'] ?? [],
+ $packagesFile['devDependencies'] ?? []
+ );
+
+ // Filter out the required packages that already exist
+ $packagesToAdd = array_diff_key($requiredPackages, $existingPackages);
+
+ // Update devDependencies with the filtered packages
+ $packagesFile['devDependencies'] = static::updatePackageArray(
+ $packagesFile['devDependencies'] ?? [],
+ $packagesToAdd
+ );
+
+ ksort($packagesFile['devDependencies']);
+
+ file_put_contents(
+ base_path('package.json'),
+ json_encode($packagesFile, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL
+ );
+ }
+
+ /**
+ * Added the required assets for Aim Admin
+ * @return void
+ */
+ protected static function updateAimAdminAssets(): void
+ {
+ // File path to the 'app.js'
+ $appFilePath = base_path('resources/js/app.js');
+ $boostrapFilePath = base_path('resources/js/bootstrap.js');
+
+ // app.js Line to be added
+ $appLineToAdd = "import '../../vendor/codecoz/aim-admin/resources/js/adminlte.js';\n";
+ $appLineToAdd .= "import '../../vendor/codecoz/aim-admin/resources/scss/adminlte.scss';\n";
+
+ // Check if the import has already been added (using a more robust method)
+ if (!self::hasImportBeenAdded($appFilePath, $appLineToAdd)) {
+ // Append the line if it does not exist
+ file_put_contents($appFilePath, $appLineToAdd, FILE_APPEND);
+ }
+
+ // bootstrap.js Line to be added
+ $boostrapLineToAdd = "import $ from 'jquery';\n";
+ $boostrapLineToAdd .= "window.$ = window.jQuery = $;\n";
+
+ // Check if the import has already been added (using a more robust method)
+ if (!self::hasImportBeenAdded($boostrapFilePath, $boostrapLineToAdd)) {
+ // Append the line if it does not exist
+ file_put_contents($boostrapFilePath, $boostrapLineToAdd, FILE_APPEND);
+ }
+ }
+
+ /**
+ * Export the Config file.
+ */
+ protected static function exportConfig(): void
+ {
+ copy(__DIR__ . '../../../config/aim-admin.php', base_path('config/aim-admin.php'));
+ }
+
+ /**
+ * Export the migrations
+ * @return void
+ */
+ protected static function exportMigrations(): void
+ {
+ $filesystem = new Filesystem();
+ $sourcePath = Admin::packagePath('database/migrations');
+ $destinationPath = database_path('migrations');
+
+ // Check if the destination directory exists
+ if ($filesystem->exists($destinationPath)) {
+ // Delete the existing directory
+ $filesystem->deleteDirectory($destinationPath);
+ }
+
+ if (!$filesystem->copyDirectory($sourcePath, $destinationPath)) {
+ error_log("Failed to copy directory from $sourcePath to $destinationPath");
+ }
+ }
+
+ /**
+ * Export User Model
+ * @return void
+ */
+
+ protected static function exportModel(): void
+ {
+ if (!is_dir($directory = app_path('Models'))) {
+ mkdir($directory, 0755, true);
+ }
+
+ $filesystem = new Filesystem;
+
+ collect($filesystem->allFiles(base_path('vendor/codecoz/aim-admin/src/stubs/app/Models')))
+ ->each(function (SplFileInfo $file) use ($filesystem) {
+ $filesystem->copy(
+ $file->getPathname(),
+ app_path('Models/' . Str::replaceLast('.stub', '.php', $file->getFilename()))
+ );
+ });
+ }
+
+ /**
+ * Added Home as named route
+ * @return void
+ */
+ protected static function addedDashboardRoute(): void
+ {
+ // Read the content of web.php.stub
+ $stubContent = file_get_contents(__DIR__ . '../../stubs/route/web.php.stub');
+
+ // Get the path to web.php
+ $webPath = base_path('routes/web.php');
+
+ // Read the current content of web.php
+ $webContent = file_get_contents($webPath);
+
+ // Check if stub content is already in web.php
+ if (!str_contains($webContent, "name('home')")) {
+ // If not, append the stub content to web.php
+ file_put_contents($webPath, $stubContent, FILE_APPEND);
+ }
+ }
+
+ /**
+ * Added home blade
+ * @return void
+ */
+ protected static function addedDashboardBlade(): void
+ {
+ copy(__DIR__ . '../../stubs/resources/views/dashboard.blade.php', resource_path('views/dashboard.blade.php'));
+ }
+
+ /**
+ * Export the vite.config.js
+ * @return void
+ */
+ protected static function exportVite(): void
+ {
+ copy(__DIR__ . '../../stubs/vite.config.js', base_path('vite.config.js'));
+ }
+
+
+ /**
+ * Helper function to check for the import more reliably
+ * @param $filePath
+ * @param $lineToAdd
+ * @return bool
+ */
+ protected static function hasImportBeenAdded($filePath, $lineToAdd): bool
+ {
+ $fileContent = file_get_contents($filePath);
+ return str_contains($fileContent, $lineToAdd);
+ }
+
+}
diff --git a/src/Console/UpgradeAimAdminCommand.php b/src/Console/UpgradeAimAdminCommand.php
new file mode 100644
index 0000000..72c4bde
--- /dev/null
+++ b/src/Console/UpgradeAimAdminCommand.php
@@ -0,0 +1,142 @@
+info('Aim Admin is upgrading. Please wait....');
+ self::exportLoadingGif();
+ self::checkMissingOrUpdatedPackages();
+ sleep(5);
+ $this->info('Aim Admin upgrade complete !');
+ }
+
+
+ /**
+ * Export the migrations
+ * @return void
+ */
+ protected static function exportLoadingGif(): void
+ {
+ $filesystem = new Filesystem();
+ $sourcePath = Admin::packagePath('resources/img/loader.gif');
+ $destinationPath = public_path('img/loader.gif');
+
+ // Check if the destination directory exists
+ if (!$filesystem->exists(dirname($destinationPath))) {
+ // Create the directory if it does not exist
+ $filesystem->makeDirectory(dirname($destinationPath), 0755, true);
+ }
+
+ // Copy the loader.gif file to the destination directory
+ if (!$filesystem->copy($sourcePath, $destinationPath)) {
+ error_log("Failed to copy loading gif from $sourcePath to $destinationPath");
+ }
+ }
+
+ /**
+ * Check for missing or updated packages and update package.json
+ * @return void
+ */
+ protected function checkMissingOrUpdatedPackages(): void
+ {
+ $this->info('Checking for missing or updated packages...');
+
+ $packagesFile = json_decode(file_get_contents(base_path('package.json')), true);
+
+ $requiredPackages = Helper::requiredPackages();
+
+ $existingPackages = array_merge(
+ $packagesFile['dependencies'] ?? [],
+ $packagesFile['devDependencies'] ?? []
+ );
+
+ $missingPackages = [];
+ $updatedPackages = [];
+
+ foreach ($requiredPackages as $package => $version) {
+ if (!isset($existingPackages[$package])) {
+ $missingPackages[$package] = $version;
+ } elseif ($existingPackages[$package] !== $version) {
+ $updatedPackages[$package] = [
+ 'current' => $existingPackages[$package],
+ 'required' => $version,
+ ];
+ }
+ }
+
+ if (empty($missingPackages) && empty($updatedPackages)) {
+ $this->info('All packages are up to date.');
+ } else {
+ if (!empty($missingPackages)) {
+ $this->info('Missing packages:');
+ foreach ($missingPackages as $package => $version) {
+ $this->line(" - $package: $version");
+ }
+ }
+
+ if (!empty($updatedPackages)) {
+ $this->info('Packages to be updated:');
+ foreach ($updatedPackages as $package => $versions) {
+ $this->line(" - $package: current version {$versions['current']}, required version {$versions['required']}");
+ }
+ }
+ // Ask for user permission before updating package.json
+ if ($this->confirm('Do you want to update package.json with the missing and updated packages?')) {
+ $this->info('Up gradation is on progress....');
+ $this->upgradePackageJson($missingPackages, $updatedPackages);
+ } else {
+ $this->info('package.json was not updated.');
+ }
+ }
+ }
+
+ /**
+ * Update package.json with missing and updated packages
+ * @param array $missingPackages
+ * @param array $updatedPackages
+ * @return void
+ */
+ protected function upgradePackageJson(array $missingPackages, array $updatedPackages): void
+ {
+ $packagesFile = json_decode(file_get_contents(base_path('package.json')), true);
+
+ // Combine all the packages to be updated
+ $packagesToUpdate = array_merge(
+ $missingPackages,
+ array_map(function ($versions) {
+ return $versions['required'];
+ }, $updatedPackages)
+ );
+
+ // Update devDependencies with the packages to be updated
+ $packagesFile['devDependencies'] = array_merge(
+ $packagesFile['devDependencies'] ?? [],
+ $packagesToUpdate
+ );
+
+ ksort($packagesFile['devDependencies']);
+
+ file_put_contents(
+ base_path('package.json'),
+ json_encode($packagesFile, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL
+ );
+
+ $this->info('package.json has been updated with missing and updated packages.');
+ }
+}
diff --git a/src/Contracts/Controller/AimAdminControllerInterface.php b/src/Contracts/Controller/AimAdminControllerInterface.php
new file mode 100644
index 0000000..a522d30
--- /dev/null
+++ b/src/Contracts/Controller/AimAdminControllerInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Controller;
+
+/**
+ * This interface defines blueprints of AimAdmin admin controller.
+ * as well as filter and sorting options.
+ *
+ * @author CodeCoz
+ */
+interface AimAdminControllerInterface
+{
+ function getRepository();
+ function initGrid(array $columns);
+}
diff --git a/src/Contracts/Field/FieldInterface.php b/src/Contracts/Field/FieldInterface.php
new file mode 100644
index 0000000..7964be0
--- /dev/null
+++ b/src/Contracts/Field/FieldInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Field;
+
+/**
+ * This interface defines blueprints of field for curd boad components i.e. grid, form and filters
+ *
+ *
+ * @author CodeCoz
+ */
+interface FieldInterface
+{
+ public static function init(string $name, ?string $label = null, mixed ...$params): self;
+
+}
diff --git a/src/Contracts/Repository/AimAdminRepositoryInterface.php b/src/Contracts/Repository/AimAdminRepositoryInterface.php
new file mode 100644
index 0000000..21feb03
--- /dev/null
+++ b/src/Contracts/Repository/AimAdminRepositoryInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Repository;
+
+/**
+ * This interface defines blueprints of AimAdmin Repository.
+ *
+ *
+ * @author CodeCoz
+ */
+interface AimAdminRepositoryInterface
+{
+ public function getModelFqcn(): string;
+ public function crudShow(int|string $id): ?\ArrayAccess;
+}
diff --git a/src/Contracts/Service/CrudBoard/CrudBoardInterface.php b/src/Contracts/Service/CrudBoard/CrudBoardInterface.php
new file mode 100644
index 0000000..69b33dd
--- /dev/null
+++ b/src/Contracts/Service/CrudBoard/CrudBoardInterface.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Service\CrudBoard;
+
+use CodeCoz\AimAdmin\Contracts\Repository\AimAdminRepositoryInterface;
+use CodeCoz\AimAdmin\Form\AbstractForm;
+
+/**
+ * This interface defines blueprints of CRUD Board. CRUD Board is will handle CRUD operations
+ * as well as filter and sorting options.
+ *
+ * @author CodeCoz
+ */
+interface CrudBoardInterface
+{
+ public function createGrid(CrudGridLoaderInterface $dataLoader, array $params = []): CrudGridInterface;
+
+ public function getRepository(): AimAdminRepositoryInterface;
+
+ public function setRepository(AimAdminRepositoryInterface $repo): self;
+
+ public function getGrid(): CrudGridInterface;
+
+ public function addGridActions(array $actions = []);
+
+ public function getForm(): AbstractForm;
+
+ public function createForm(array $fields);
+
+ public function getCrudShow(): CrudShowInterface;
+
+}
diff --git a/src/Contracts/Service/CrudBoard/CrudFormHandlerInterface.php b/src/Contracts/Service/CrudBoard/CrudFormHandlerInterface.php
new file mode 100644
index 0000000..7d71ca8
--- /dev/null
+++ b/src/Contracts/Service/CrudBoard/CrudFormHandlerInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Service\CrudBoard;
+
+/**
+ * This interface defines blueprints of AimAdmin CRUD form data handler.
+ * It defines how to validate and store data to DB
+ * It will ensure to provide record for CrudBoard grid.
+ *
+ * @author CodeCoz
+ */
+interface CrudFormHandlerInterface
+{
+ function saveFormData(array $data = []);
+}
diff --git a/src/Contracts/Service/CrudBoard/CrudGridInterface.php b/src/Contracts/Service/CrudBoard/CrudGridInterface.php
new file mode 100644
index 0000000..30fcff9
--- /dev/null
+++ b/src/Contracts/Service/CrudBoard/CrudGridInterface.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Service\CrudBoard;
+
+use CodeCoz\AimAdmin\Services\CrudBoard\GridFilter;
+use Illuminate\Pagination\LengthAwarePaginator;
+
+/**
+ * This interface defines blueprints of AimAdmin CRUD grid.
+ * It will ensure to provide record for CrudBoard grid.
+ *
+ * @author CodeCoz
+ */
+interface CrudGridInterface
+{
+ function getGridDataLoader(): CrudGridLoaderInterface;
+
+ function getGridData(): LengthAwarePaginator;
+
+ function addColumns(array $columns);
+
+ function getColumns();
+
+ function addActions(array $actions = []);
+
+ public function getFilter(): GridFilter;
+
+ public function setTitle(string $title): self;
+
+ public function getTitle(): ?string;
+
+ public static function init(CrudGridLoaderInterface $gridDataLoader, array $params): CrudGridInterface;
+
+}
diff --git a/src/Contracts/Service/CrudBoard/CrudGridLoaderInterface.php b/src/Contracts/Service/CrudBoard/CrudGridLoaderInterface.php
new file mode 100644
index 0000000..1d2e658
--- /dev/null
+++ b/src/Contracts/Service/CrudBoard/CrudGridLoaderInterface.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Service\CrudBoard;
+
+/**
+ * This interface defines blueprints of AimAdmin CRUD grid loader.
+ * It will ensure to provide record for CrudBoard grid.
+ *
+ * @author CodeCoz
+ */
+
+use Illuminate\Pagination\CursorPaginator;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Collection;
+
+interface CrudGridLoaderInterface
+{
+ public function getGridData(array $filters = []): ?iterable;
+
+ public function getGridQuery(): ?Builder;
+
+ public function getGridPaginator(array $filters): ?LengthAwarePaginator;
+
+ public function getGridCursorPaginator(array $filters): ?CursorPaginator;
+
+ public function applyFilterQuery(Builder $query, array $filters): ?Builder;
+
+ public function applyFilterData(Collection $data, array $filters): Collection;
+}
diff --git a/src/Contracts/Service/CrudBoard/CrudShowInterface.php b/src/Contracts/Service/CrudBoard/CrudShowInterface.php
new file mode 100644
index 0000000..ab6a09e
--- /dev/null
+++ b/src/Contracts/Service/CrudBoard/CrudShowInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Contracts\Service\CrudBoard;
+
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use CodeCoz\AimAdmin\Collection\FieldCollection;
+
+/**
+ * This interface defines blueprints of AimAdmin CRUD grid loader.
+ * It will ensure to provide record for CrudBoard grid.
+ *
+ * @author CodeCoz
+ */
+interface CrudShowInterface
+{
+ function getActions(): ActionCollection;
+
+ function getFields(): FieldCollection;
+
+ function getRecord(): \ArrayAccess;
+}
diff --git a/src/Controller/AbstractAimAdminController.php b/src/Controller/AbstractAimAdminController.php
new file mode 100644
index 0000000..0924531
--- /dev/null
+++ b/src/Controller/AbstractAimAdminController.php
@@ -0,0 +1,109 @@
+actionList = $this->actionList ?? $this->configureActions();
+ $grid = CrudBoardFacade::createGrid($this->getRepository(), $params)
+ ->addColumns($columns)
+ ->addActions($this->actionList);
+ $this->configureFilter();
+ return $grid;
+ }
+
+ protected function getForm(array $fields)
+ {
+ $this->actionList = $this->actionList ?? $this->configureActions();
+ return CrudBoardFacade::createForm($fields)
+ ->setFormStat(CrudForm::STAT_NEW)
+ ->setActions($this->actionList);
+ }
+
+ protected function getFilter(array $fields)
+ {
+ $this->actionList = $this->actionList ?? $this->configureActions();
+ return CrudBoardFacade::getGrid()
+ ->getFilter()
+ ->addFields($fields)
+ ->setFormStat(CrudForm::STAT_NEW)
+ ->setActions($this->actionList)
+ ->assignQueryData();
+ }
+
+ protected function initCreate()
+ {
+ $this->configureForm();
+ return CrudBoardFacade::getForm();
+ }
+
+
+ protected function initStore(Request $request)
+ {
+ $this->configureForm();
+ return CrudBoardFacade::getForm()
+ ->setFormHandler($this->getRepository())
+ ->processData($request);
+ }
+
+ protected function getDefaultCrudActions(): iterable
+ {
+ return [
+
+ ];
+ }
+
+ protected function initEdit(mixed $row): CrudForm
+ {
+ $repo = $this->getRepository();
+ $model = ($row instanceof Model) ? $row : $repo->getRecordForEdit($row);
+ $this->configureForm();
+ return CrudBoardFacade::getForm()
+ ->setFormStat(CrudForm::STAT_EDIT)
+ ->setData($model);
+ }
+
+ protected function initShow(mixed $row, array $fields)
+ {
+ $this->actionList = $this->actionList ?? $this->configureActions();
+ return CrudBoardFacade::setRepository($this->getRepository())
+ ->createShow($row, $fields)
+ ->addActions($this->actionList);
+ }
+
+ public function configureActions(): iterable
+ {
+ return $this->getDefaultCrudActions();
+ }
+
+}
+
diff --git a/src/Driver/AimAdminUserProviderDriver.php b/src/Driver/AimAdminUserProviderDriver.php
new file mode 100644
index 0000000..8d39e0a
--- /dev/null
+++ b/src/Driver/AimAdminUserProviderDriver.php
@@ -0,0 +1,58 @@
+is_successful ?? true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ public function updateRememberToken(Authenticatable $user, $token): void
+ {
+ $userData = session('user');
+ if ($userData && $userData['id'] == $user->getAuthIdentifier()) {
+ $userData['remember_token'] = $token;
+ session(['user' => $userData]);
+ }
+ }
+
+ public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false)
+ {
+
+ }
+}
+
diff --git a/src/Dto/CrudBoard/AbstractHtmlElementDto.php b/src/Dto/CrudBoard/AbstractHtmlElementDto.php
new file mode 100644
index 0000000..731e1c8
--- /dev/null
+++ b/src/Dto/CrudBoard/AbstractHtmlElementDto.php
@@ -0,0 +1,312 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Dto\CrudBoard;
+
+
+use CodeCoz\AimAdmin\Support\OptionValueStore;
+
+/**
+ * This base class is for crudboard DTO. This is a base class and contains
+ * common methods and attributes of all dto.
+ *
+ * @author CodeCoz
+ */
+abstract class AbstractHtmlElementDto
+{
+ // these are the actions applied to a specific row
+ public const TYPE_ROW = 'row';
+ // these are the actions that are not associated to an crud board
+ // (they are available only in the INDEX page)
+ public const TYPE_CRUD_BOARD = 'crud_board';
+ // these are actions that can be applied to one or more rows at the same time
+ public const TYPE_BATCH = 'batch';
+
+ public const TYPE_FORM = 'form';
+
+ public const TYPE_SHOW = 'show';
+
+ // these are actions that can be applied to filter
+ public const TYPE_FILTER = 'filter';
+
+ const INPUT_TYPE_HIDDEN = 'hidden';
+ const INPUT_TYPE_FILE = 'file';
+
+ public const GRID_DELETE_ACTION = 'grid-delete';
+
+ private ?string $name = null;
+ private $label;
+ private $help;
+ private ?string $icon = null;
+ private string $cssClass = '';
+ private ?string $textAlign = null;
+ private OptionValueStore $htmlAttributes;
+ private ?string $htmlElement = null;
+ private $formatValueCallable;
+ private $formattedValue;
+ private $displayCallable;
+ private string $component;
+ private ?bool $disable = null;
+ private ?bool $readonly = null;
+ private ?string $placeholder = null;
+ private mixed $value = null;
+ private ?string $layoutClass = null;
+ private bool $required = false;
+ private OptionValueStore $options;
+
+ public function __construct()
+ {
+ $this->htmlAttributes = OptionValueStore::init();
+ $this->options = OptionValueStore::init();
+
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ public function getValue(): mixed
+ {
+ return $this->value;
+ }
+
+ public function setValue(mixed $value): void
+ {
+ $this->value = $value;
+ }
+
+ public function setLabel(mixed $label): void
+ {
+ $this->label = \is_string($label) ? \ucwords(\str_replace('_', ' ', $label)) : $label;
+ }
+
+ public function getCssClass(): string
+ {
+ return $this->cssClass;
+ }
+
+ public function setCssClass(string $cssClass): void
+ {
+ $this->cssClass = $cssClass;
+ }
+
+ public function getComponent(): string
+ {
+ return $this->component;
+ }
+
+ public function setComponent(string $component): void
+ {
+ $this->component = $component;
+ }
+
+ public function getFormatValueCallable(): ?callable
+ {
+ return $this->formatValueCallable;
+ }
+
+ public function setFormatValueCallable(?callable $callable): void
+ {
+ $this->formatValueCallable = $callable;
+ }
+
+ public function getFormattedValue(): mixed
+ {
+ $this->formattedValue = ($this->formatValueCallable && $this->value) ? \call_user_func($this->formatValueCallable, $this->value)
+ : $this->value ?? $this->formattedValue;
+ return $this->formattedValue;
+ }
+
+ public function setFormattedValue(mixed $formattedValue): void
+ {
+ $this->formattedValue = $formattedValue;
+ }
+
+ public function getTextAlign(): ?string
+ {
+ return $this->textAlign;
+ }
+
+ public function setTextAlign(string $textAlign): void
+ {
+ $this->textAlign = $textAlign;
+ }
+
+ public function shouldBeDisplayedFor(mixed $row): bool
+ {
+ return null === $this->displayCallable || (bool)\call_user_func($this->displayCallable, $row);
+ }
+
+ public function setDisplayCallable(callable $displayCallable): void
+ {
+ $this->displayCallable = $displayCallable;
+ }
+
+ public function isDisabled(): ?bool
+ {
+ return $this->disable;
+ }
+
+ public function setDisable(bool $disable): void
+ {
+ $this->disable = $disable;
+ }
+
+ public function isReadonly(): ?bool
+ {
+ return $this->readonly;
+ }
+
+ public function setReadonly(bool $readonly): void
+ {
+ $this->readonly = $readonly;
+ }
+
+ public function isRequired(): ?bool
+ {
+ return $this->required;
+ }
+
+ public function setRequired(bool $required): void
+ {
+ $this->required = $required;
+ }
+
+
+ public function getHtmlElement(): string
+ {
+ return $this->htmlElement;
+ }
+
+ public function setHtmlElement(string $htmlElement): void
+ {
+ $this->htmlElement = $htmlElement;
+ }
+
+ public function getHtmlAttributes(): OptionValueStore
+ {
+ return $this->htmlAttributes;
+ }
+
+ public function addHtmlAttributes(array $htmlAttributes): void
+ {
+ $this->htmlAttributes->add($this->htmlAttributes, $htmlAttributes);
+ }
+
+ public function setHtmlAttributes(array $htmlAttributes): void
+ {
+ $this->htmlAttributes->set($htmlAttributes);
+ }
+
+ public function setHtmlAttribute(string $attributeName, mixed $attributeValue): void
+ {
+ $this->htmlAttributes->set($attributeName, $attributeValue);
+ }
+
+ public function getIcon(): ?string
+ {
+ return $this->icon;
+ }
+
+ public function setIcon(?string $icon): void
+ {
+ $this->icon = $icon;
+ }
+
+ public function getHelp(): string|null
+ {
+ return $this->help;
+ }
+
+ public function setHelp(string $help): void
+ {
+ $this->help = $help;
+ }
+
+ public function getAttributesAsHtml(): string
+ {
+ $html = '';
+ $attributes = $this->htmlAttributes->get();
+ foreach ($attributes as $attr => $val) {
+ $html .= $this->prepareHtmlAttributes($attr, $val);
+ }
+ // echo $html;
+ return $html;
+ }
+
+ private function prepareHtmlAttributes(string $attr, mixed $val): string
+ {
+ $html = '';
+ switch (strtolower($attr)) {
+ case 'class':
+ $this->cssClass .= " $val ";
+ break;
+ case 'readonly':
+ $this->readonly = (bool)$val;
+ break;
+ case 'disabled':
+ $this->disable = (bool)$val;
+ break;
+ case 'required':
+ $this->required = (bool)$val;
+ break;
+ case 'placeholder':
+ $this->placeholder = $val;
+ break;
+ default:
+ $html = $attr . '="' . $val . '"';
+ }
+ return $html;
+ }
+
+
+ public function setPlaceholder(string $placeholder): void
+ {
+ $this->placeholder = $placeholder;
+ }
+
+ public function getPlaceholder(): ?string
+ {
+ return $this->placeholder;
+ }
+
+ public function setLayoutClass(string $class): void
+ {
+ $this->layoutClass = $class;
+ }
+
+ public function getLayoutClass(): ?string
+ {
+ return $this->layoutClass;
+ }
+
+ public function getOption($name): mixed
+ {
+ return $this->options->get($name);
+ }
+
+ public function setOption(mixed $name, mixed $value): void
+ {
+ $this->options->set($name, $value);
+ }
+
+}
diff --git a/src/Dto/CrudBoard/ActionDto.php b/src/Dto/CrudBoard/ActionDto.php
new file mode 100644
index 0000000..7ed36f8
--- /dev/null
+++ b/src/Dto/CrudBoard/ActionDto.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Dto\CrudBoard;
+
+
+/**
+ * This class is for action DTO of grid board .
+ *
+ * @author CodeCoz
+ */
+final class ActionDto extends AbstractHtmlElementDto
+{
+ private ?string $type = null;
+ private $url;
+ private ?string $routeName = null;
+ private $routeParameters = [];
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function setType(string $type): void
+ {
+ $this->type = $type;
+ }
+
+ public function getUrl(): string|callable
+ {
+ return $this->url;
+ }
+
+ public function setUrl(string|callable $url): void
+ {
+ $this->url = $url;
+ }
+
+ public function isRowAction(): bool
+ {
+ return self::TYPE_ROW === $this->type;
+ }
+
+ public function isCrudBoardAction(): bool
+ {
+ return self::TYPE_CRUD_BOARD === $this->type;
+ }
+
+ public function isBatchAction(): bool
+ {
+ return self::TYPE_BATCH === $this->type;
+ }
+
+ public function isFormAction(): bool
+ {
+ return self::TYPE_FORM === $this->type;
+ }
+
+ public function isShowAction(): bool
+ {
+ return self::TYPE_SHOW === $this->type;
+ }
+
+ public function isGridDeleteAction(): bool
+ {
+ return self::GRID_DELETE_ACTION == $this->getOption('role');
+ }
+
+ public function getRouteName(): ?string
+ {
+ return $this->routeName;
+ }
+
+ public function setRouteName(string $routeName): void
+ {
+ $this->routeName = $routeName;
+ }
+
+ public function getRouteParameters(): array|\Closure
+ {
+ return $this->routeParameters;
+ }
+
+ public function setRouteParameters(array|\Closure $routeParameters): void
+ {
+ $this->routeParameters = $routeParameters;
+ }
+
+ public function isSubmitAction(): bool
+ {
+ return $this->getHtmlAttributes()->get('type', '') === 'submit';
+ }
+
+ public function isFilterAction(): bool
+ {
+ return self::TYPE_FILTER === $this->type;
+ }
+
+ public function isButton(): bool
+ {
+ return $this->getHtmlElement() == 'button';
+ }
+
+}
+
diff --git a/docs/contents/filter.md b/src/Dto/CrudBoard/FieldCollectionDto.php
similarity index 100%
rename from docs/contents/filter.md
rename to src/Dto/CrudBoard/FieldCollectionDto.php
diff --git a/src/Dto/CrudBoard/FieldDto.php b/src/Dto/CrudBoard/FieldDto.php
new file mode 100644
index 0000000..181f11f
--- /dev/null
+++ b/src/Dto/CrudBoard/FieldDto.php
@@ -0,0 +1,269 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Dto\CrudBoard;
+
+use CodeCoz\AimAdmin\Support\OptionValueStore;
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author CodeCoz
+ */
+final class FieldDto
+{
+ private ?string $name = null;
+ private mixed $value = null;
+ private mixed $formattedValue = null;
+ private $formatValueCallable;
+ private string $label;
+ private string $cssClass = '';
+ private ?string $textAlign = null;
+ private ?string $help;
+ private ?bool $sortable = null;
+ private ?bool $virtual = null;
+ private ?string $component = null;
+ private ?bool $hidden = null;
+ private ?bool $disable = null;
+ private ?string $inputType = null;
+ private OptionValueStore $customOptions;
+ private string $validationRule = '';
+ private string $placeholder;
+ private OptionValueStore $htmlAttributes;
+
+ public function __construct()
+ {
+ $this->customOptions = OptionValueStore::init();
+ $this->htmlAttributes = OptionValueStore::init();
+
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): void
+ {
+ $this->name = $name;
+ }
+
+ public function getValue(): mixed
+ {
+ return $this->value;
+ }
+
+ public function setValue(mixed $value): void
+ {
+ $this->value = $value;
+ }
+
+
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ public function setLabel($label): void
+ {
+ $this->label = \ucwords(\str_replace('_', ' ', $label));
+ }
+
+ public function getFormatValueCallable(): ?callable
+ {
+ return $this->formatValueCallable;
+ }
+
+ public function setFormatValueCallable(?callable $callable): void
+ {
+ $this->formatValueCallable = $callable;
+ }
+
+ public function getTextAlign(): ?string
+ {
+ return $this->textAlign;
+ }
+
+ public function setTextAlign(string $textAlign): void
+ {
+ $this->textAlign = $textAlign;
+ }
+
+ public function getCssClass(): string
+ {
+ return $this->cssClass;
+ }
+
+ public function setCssClass(string $cssClass): void
+ {
+ $this->cssClass = trim($cssClass);
+ }
+
+ public function getComponent(): ?string
+ {
+ return $this->component;
+ }
+
+ public function setComponent(?string $component): void
+ {
+ $this->component = $component;
+ }
+
+ public function getHelp(): string|null
+ {
+ return $this->help;
+ }
+
+ public function setHelp(string $help): void
+ {
+ $this->help = $help;
+ }
+
+ public function isSortable(): ?bool
+ {
+ return $this->sortable;
+ }
+
+ public function setSortable(bool $isSortable): void
+ {
+ $this->sortable = $isSortable;
+ }
+
+ public function isHidden(): ?bool
+ {
+ return $this->hidden;
+ }
+
+ public function setHidden(bool $hidden): void
+ {
+ $this->hidden = $hidden;
+ }
+
+ public function isDisabled(): ?bool
+ {
+ return $this->disable;
+ }
+
+ public function setDisable(bool $disable): void
+ {
+ $this->disable = $disable;
+ }
+
+ public function isVirtual(): ?bool
+ {
+ return $this->virtual;
+ }
+
+ public function setVirtual(bool $isVirtual): void
+ {
+ $this->virtual = $isVirtual;
+ }
+
+ public function getFormattedValue(): mixed
+ {
+ return $this->formattedValue;
+ }
+
+ public function setFormattedValue(mixed $formattedValue): void
+ {
+ $this->formattedValue = $formattedValue;
+ }
+
+ public function setInputType(string $inputType): void
+ {
+ $this->inputType = $inputType;
+ }
+
+ public function getInputType(): ?string
+ {
+ return $this->inputType;
+ }
+
+ public function setCustomOptions(array $customOptions): void
+ {
+ $this->customOptions = OptionValueStore::init($customOptions);
+ }
+
+ public function setCustomOption(string $optionName, mixed $optionValue): void
+ {
+ $this->customOptions->set($optionName, $optionValue);
+ }
+
+ public function getCustomOption(string $optionName): string|int|null
+ {
+ return $this->customOptions->get($optionName);
+ }
+
+ public function getCustomOptions(): mixed
+ {
+ return $this->customOptions->all();
+ }
+
+ public function setValidationRule(string $rule): void
+ {
+ $this->validationRule = $rule;
+ }
+
+ public function getValidationRule(): string
+ {
+ return $this->validationRule;
+ }
+
+ public function getHtmlAttributes(): OptionValueStore
+ {
+ return $this->htmlAttributes;
+ }
+
+ public function addHtmlAttributes(array $htmlAttributes): void
+ {
+ $this->htmlAttributes->add($this->htmlAttributes, $htmlAttributes);
+ }
+
+ public function setHtmlAttributes(array $htmlAttributes): void
+ {
+ $this->htmlAttributes->set($htmlAttributes);
+ }
+
+ public function setHtmlAttribute(string $attributeName, mixed $attributeValue): void
+ {
+ $this->htmlAttributes->set($attributeName, $attributeValue);
+ }
+
+ public function getAttributesAsHtml(): string
+ {
+ $html = '';
+ $attributes = $this->htmlAttributes->get();
+ foreach ($attributes as $attr => $val) {
+ $html .= $this->prepareHtmlAttributes($attr, $val);
+ }
+ return $html;
+ }
+
+ private function prepareHtmlAttributes(string $attr, mixed $val): string
+ {
+ $html = '';
+ switch (strtolower($attr)) {
+ case 'class':
+ $this->cssClass .= " $val ";
+ break;
+ case 'disabled':
+ $this->disable = (bool)$val;
+ break;
+ case 'placeholder':
+ $this->placeholder = $val;
+ break;
+ default:
+ $html = " $attr = $val ";
+ }
+ return $html;
+ }
+
+}
diff --git a/src/Dto/CrudBoard/FormFieldDto.php b/src/Dto/CrudBoard/FormFieldDto.php
new file mode 100644
index 0000000..799d0bb
--- /dev/null
+++ b/src/Dto/CrudBoard/FormFieldDto.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Dto\CrudBoard;
+
+use CodeCoz\AimAdmin\Support\OptionValueStore;
+
+final class FormFieldDto extends AbstractHtmlElementDto
+{
+ private OptionValueStore $customOptions;
+ private $validationRule;
+ private ?string $inputType = null;
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->customOptions = OptionValueStore::init();
+ }
+
+ public function setCustomOptions(array $customOptions): void
+ {
+ $this->customOptions->set($customOptions);
+ }
+
+ public function setCustomOption(string $optionName, mixed $optionValue): void
+ {
+ $this->customOptions->set($optionName, $optionValue);
+ }
+
+ public function getCustomOptions(): array
+ {
+ return $this->customOptions->all();
+ }
+
+ public function getCustomOptionStore(): OptionValueStore
+ {
+ return $this->customOptions;
+ }
+
+ public function getCustomOption(string $optionName): mixed
+ {
+ return $this->customOptions->get($optionName);
+ }
+
+ public function setValidationRule(array|string $rules)
+ {
+ $this->validationRule = $rules;
+ }
+
+ public function getValidationRule(): null|array|string
+ {
+ return $this->validationRule;
+ }
+
+ public function setInputType(string $inputType): void
+ {
+ $this->inputType = $inputType;
+ }
+
+ public function getInputType(): ?string
+ {
+ return $this->inputType;
+ }
+
+ public function isHiddenInput()
+ {
+ return $this->inputType == self::INPUT_TYPE_HIDDEN;
+ }
+
+ public function isFileInput()
+ {
+ return $this->inputType == self::INPUT_TYPE_FILE;
+ }
+
+}
diff --git a/src/Facades/Admin.php b/src/Facades/Admin.php
new file mode 100644
index 0000000..c4bcb5a
--- /dev/null
+++ b/src/Facades/Admin.php
@@ -0,0 +1,18 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+use function Symfony\Component\String\u;
+use CodeCoz\AimAdmin\Dto\CrudBoard\ActionDto;
+
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class ButtonField
+{
+ public const DELETE = 'delete';
+ public const DETAIL = 'detail';
+ public const EDIT = 'edit';
+ public const INDEX = 'index';
+ public const NEW = 'new';
+
+
+ private ActionDto $dto;
+
+ private function __construct(ActionDto $actionDto)
+ {
+ $this->dto = $actionDto;
+ }
+
+ public function __toString()
+ {
+ return $this->dto->getName();
+ }
+
+ /**
+ * @param string $name
+ * @param string|null $label
+ * @param mixed ...$params
+ * @return self
+ */
+ public static function init(string $name, ?string $label = null, mixed ...$params): self
+ {
+ $dto = new ActionDto();
+ $dto->setType($params['type'] ?? ActionDto::TYPE_ROW);
+ $dto->setName($name);
+ $dto->setValue($name);
+ $dto->setLabel($label);
+ $dto->setComponent('aim-admin::crudboard.actions.grid-button');
+ switch ($name) {
+ case self::EDIT :
+ $dto->setIcon('fa-pen-to-square');
+ $dto->setCssClass('btn btn-block btn-sm btn-primary');
+ break;
+ case self::DELETE :
+ $dto->setIcon('fa-times');
+ $dto->setOption('role', ActionDto::GRID_DELETE_ACTION);
+ $dto->setCssClass('btn btn-block btn-sm btn-danger');
+ $dto->setComponent('aim-admin::crudboard.actions.grid-delete-button');
+ break;
+ case self::DETAIL :
+ $dto->setIcon('fa-file-lines');
+ $dto->setCssClass('btn btn-block btn-sm btn-info');
+ break;
+ default:
+ $dto->setCssClass('btn btn-block btn-sm btn-primary');
+ break;
+ }
+ isset($params['icon']) && $dto->setIcon($params['icon']);
+ isset($params['cssClass']) && $dto->setCssClass($params['cssClass']);
+ $dto->setHtmlElement('a');
+ $dto->setHtmlAttribute('title', $label ?? self::humanizeString($name));
+ return new self($dto);
+ }
+
+ public function createAsCrudBoardAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_CRUD_BOARD);
+ $this->dto->setCssClass('btn btn-sm btn-primary');
+ $this->iconForNew();
+ $this->dto->setComponent('aim-admin::crudboard.actions.board-action');
+ return $this;
+ }
+
+ public function iconForNew(): self
+ {
+ $this->dto->setIcon('fa-plus');
+ return $this;
+ }
+
+ public function createAsBatchAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_BATCH);
+ return $this;
+ }
+
+ public function createAsFormAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_FORM);
+ $this->dto->setCssClass('btn btn-sm btn-secondary');
+ $label = $this->dto->getLabel() ?? $this->dto->getHtmlAttributes()->get('title');
+ $this->dto->setLabel($label);
+ $this->dto->setComponent("aim-admin::crudboard.actions.form-action");
+ return $this;
+ }
+
+ public function createAsFilterAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_FILTER);
+ $this->dto->setCssClass('btn btn-sm btn-secondary');
+ $this->setComponent("aim-admin::crudboard.actions.filter-action");
+ return $this;
+ }
+
+ public function createAsFilterSubmitAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_FILTER);
+ $this->dto->setCssClass('btn btn-sm btn-success float-right');
+ $this->dto->setHtmlElement('button');
+ $this->setHtmlAttributes(['type' => 'submit'])
+ ->setComponent("aim-admin::crudboard.actions.filter-action");
+ return $this;
+ }
+
+
+ public function createAsFormSubmitAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_FORM);
+ $this->dto->setHtmlElement('button');
+ $this->dto->setCssClass('btn btn-sm float-right btn-primary');
+ $label = $this->dto->getLabel() ?? $this->dto->getHtmlAttributes()->get('title');
+ $this->dto->setLabel($label);
+ $this->setHtmlAttributes(['type' => 'submit'])
+ ->setComponent("aim-admin::crudboard.actions.form-action");
+ return $this;
+ }
+
+ public function createAsShowAction(): self
+ {
+ $this->dto->setType(ActionDto::TYPE_SHOW);
+ $this->dto->setCssClass('btn btn-sm btn-secondary');
+ $this->dto->setComponent('aim-admin::crudboard.actions.show-button');
+ return $this;
+ }
+
+ /**
+ * @param mixed $label Use FALSE to hide the label; use NULL to autogenerate it
+ */
+ public function setLabel(string|false|null $label): self
+ {
+ $this->dto->setLabel($label ?? self::humanizeString($this->dto->getName()));
+ return $this;
+ }
+
+ public function setIcon(?string $icon): self
+ {
+ $this->dto->setIcon($icon);
+
+ return $this;
+ }
+
+ public function removeCssClass(string $cssClass): self
+ {
+ $classStr = $this->dto->getCssClass();
+ if ($classStr) {
+ $classes = explode(' ', $classStr);
+ $newClasses = array_filter($classes, function ($value) use ($cssClass) {
+ return (trim($value) != $cssClass);
+ });
+ $this->dto->setCssClass(implode(' ', $newClasses));
+ }
+ return $this;
+ }
+
+ /**
+ * If you set your own CSS classes, the default CSS classes are not applied.
+ * You may want to also add the 'btn' (and 'btn-primary', etc.) classes to make
+ * your action look like a button.
+ */
+ public function setCssClass(string $cssClass): self
+ {
+ $this->dto->setCssClass($cssClass);
+ return $this;
+ }
+
+ /**
+ * If you add a custom CSS class, the default CSS classes are not applied.
+ * You may want to also add the 'btn' (and 'btn-primary', etc.) classes to make
+ * your action look like a button.
+ */
+ public function addCssClass(string $cssClass): self
+ {
+ $this->dto->setCssClass(trim($this->dto->getCssClass() . ' ' . $cssClass));
+
+ return $this;
+ }
+
+ public function displayAsLink(): self
+ {
+ $this->dto->setHtmlElement('a');
+ return $this;
+ }
+
+ public function displayAsButton(): self
+ {
+ $this->dto->setHtmlElement('button');
+
+ return $this;
+ }
+
+ public function setHtmlAttributes(array $attributes): self
+ {
+ $this->dto->setHtmlAttributes($attributes);
+
+ return $this;
+ }
+
+ public function setComponent(string $component): self
+ {
+ $this->dto->setComponent($component);
+
+ return $this;
+ }
+
+ /**
+ * @param string $routeName
+ * @param array|\Closure $routeParameters The callable has the signature: function ($row): array
+ *
+ * Route parameters can be defined as a callable with the signature: function ($entityInstance): array
+ * Example: ->linkToRoute('invoice_send', fn (Invoice $entity) => ['uuid' => $entity->getId()]);
+ * @return ButtonField
+ */
+ public function linkToRoute(string $routeName, array|\Closure $routeParameters = []): self
+ {
+ $this->dto->setRouteName($routeName);
+ $this->dto->setRouteParameters($routeParameters);
+ return $this;
+ }
+
+ public function linkToUrl(string|callable $url): self
+ {
+
+ if (is_callable($url)) {
+ $url = $url();
+ }
+
+ $this->dto->setUrl($url);
+
+ return $this;
+ }
+
+ public function displayIf(callable $callable): self
+ {
+ $this->dto->setDisplayCallable($callable);
+
+ return $this;
+ }
+
+ public function getDto(): ActionDto
+ {
+ // if (null === $this->dto->getLabel() || null === $this->dto->getIcon()) {
+ // throw new \InvalidArgumentException(sprintf('The label or icon of an action cannot be null at the same time. Either set the label, the icon or both for the "%s" action.', $this->dto->getName()));
+ // }
+ return $this->dto;
+ }
+
+ public function setStyle(string $style): self
+ {
+ $this->dto->setHtmlAttribute('style', $style);
+ return $this;
+ }
+
+ public function addStyle(string $style): self
+ {
+ $this->dto->addHtmlAttributes(['style' => $style]);
+ return $this;
+ }
+
+ public function setTitle(string $title): self
+ {
+ $this->dto->setHtmlAttribute('title', $title);
+ return $this;
+ }
+
+
+ private static function humanizeString(string $string): string
+ {
+ $uString = u($string);
+ $upperString = $uString->upper()->toString();
+
+ // this prevents humanizing all-uppercase labels (e.g. 'UUID' -> 'U u i d')
+ // and other special labels which look better in uppercase
+ if ($uString->toString() === $upperString) {
+ return $upperString;
+ }
+
+ return $uString
+ ->replaceMatches('/([A-Z])/', '_$1')
+ ->replaceMatches('/[_\s]+/', ' ')
+ ->trim()
+ ->lower()
+ ->title(true)
+ ->toString();
+ }
+
+ public function asModal(): self
+ {
+ $this->dto->setHtmlAttributes(['data-target' => '#AimAdmin-modal', 'data-toggle' => 'modal']);
+ $this->dto->setCssClass(trim($this->dto->getCssClass() . ' AimAdmin-modal'));
+ return $this;
+ }
+}
diff --git a/src/Field/ChainSelectField.php b/src/Field/ChainSelectField.php
new file mode 100644
index 0000000..041ddf5
--- /dev/null
+++ b/src/Field/ChainSelectField.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+use CodeCoz\AimAdmin\Collection\FieldCollection;
+
+/**
+ * This class is for creating chai select field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class ChainSelectField
+{
+ use FormFieldTrait;
+
+ public static function init(iterable $fields, string $route, ?string $name = null): self
+ {
+ foreach ($fields as $key => $child) {
+ $prefix = $name ? $name . '-' : "";
+ $prefix .= 'chain-';
+ if (isset($fields[($key + 1)])) {
+ $child->setCustomOption('dependant', $prefix . ($key + 1));
+ }
+ $child->setAttribute('data-chain-select', $prefix . $key);
+ }
+ $children = FieldCollection::init($fields);
+ return (new self())
+ ->setName($name ?? 'chain')
+ ->setCssClass('row')
+ ->setComponent('aim-admin::crudboard.fields.chainselect')
+ ->setCustomOption('dependant-route', $route)
+ ->setCustomOption('children', $children);
+
+ }
+
+}
diff --git a/src/Field/CheckBoxField.php b/src/Field/CheckBoxField.php
new file mode 100644
index 0000000..fbccd67
--- /dev/null
+++ b/src/Field/CheckBoxField.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class CheckBoxField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.checkbox')
+ ->setHtmlElementName('input')
+ ->setPlaceholder($label ?? self::humanizeString($name))
+ ->setInputType('checkbox')
+ ->setLabel($label ?? self::humanizeString($name))
+ ->setAttribute('id', $name);
+ }
+
+
+}
diff --git a/src/Field/ChoiceField.php b/src/Field/ChoiceField.php
new file mode 100644
index 0000000..73d0748
--- /dev/null
+++ b/src/Field/ChoiceField.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class ChoiceField implements FieldInterface
+{
+ final const LIST = 'choiceList';
+ final const TYPE = 'choiceType';
+ final const EMPTY = 'empty';
+ final const SELECTED = 'selected';
+
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ $type = $params[0] ?? ($params[self::TYPE] ?? 'select');
+ $choiceList = $params[1] ?? ($params[self::LIST] ?? null);
+ $empty = $params[2] ?? ($params[self::EMPTY] ?? null);
+ $selected = $params[3] ?? ($params[self::SELECTED] ?? null);
+ (null === $choiceList) && throw new \InvalidArgumentException(self::LIST . ':[] is a mandatory parameter');
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.choice')
+ ->setLabel($label ?? self::humanizeString($name))
+ ->setCustomOption(self::LIST, $choiceList)
+ ->setCustomOption(self::EMPTY, $empty)
+ ->setDefault($selected)
+ ->setInputType($type);
+ }
+
+ public function setDefault(mixed $defaultValue): self
+ {
+
+ if (is_array($defaultValue)) {
+ $this->setAttribute('multiple', false);
+ $values = \array_flip($defaultValue);
+ } else {
+ $values[$defaultValue] = 1;
+ }
+ $this->setCustomOption(self::SELECTED, $values);
+ return $this;
+ }
+
+ public function setList(array $list): self
+ {
+ $this->setCustomOption(self::LIST, $list);
+ return $this;
+ }
+
+
+}
diff --git a/src/Field/DateTimeField.php b/src/Field/DateTimeField.php
new file mode 100644
index 0000000..322a0e2
--- /dev/null
+++ b/src/Field/DateTimeField.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class DateTimeField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ return (new self())
+ ->setName($name)
+ ->setLabel($label ?? self::humanizeString($name))
+ ->setComponent('aim-admin::crudboard.fields.datetime')
+ ->formatValue(fn($value): string => (new \DateTime($value))->format('Y-m-d'));
+ }
+
+}
diff --git a/src/Field/Field.php b/src/Field/Field.php
new file mode 100644
index 0000000..55f899f
--- /dev/null
+++ b/src/Field/Field.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+
+final class Field implements FieldInterface
+{
+ use FieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ return (new self())
+ ->setName($name)
+ ->setLabel($label)
+ ->setComponent('aim-admin::crudboard.fields.grid_cell');
+ }
+
+}
diff --git a/src/Field/FieldGroup.php b/src/Field/FieldGroup.php
new file mode 100644
index 0000000..c6bf0a3
--- /dev/null
+++ b/src/Field/FieldGroup.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+use CodeCoz\AimAdmin\Collection\FieldCollection;
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class FieldGroup
+{
+ use FormFieldTrait;
+
+ public static function init(iterable $fields, ?string $name = null, ?string $label = null): self
+ {
+ $fields = FieldCollection::init($fields);
+
+ return (new self())
+ ->setCssClass('border rounded m-auto row')
+ ->setComponent('aim-admin::crudboard.fields.group')
+ ->setName($name ?? 'group')
+ ->setLabel($label ?? $name ?? 'Group')
+ ->setCustomOption('fields', $fields);
+ }
+
+}
diff --git a/src/Field/FieldTrait.php b/src/Field/FieldTrait.php
new file mode 100644
index 0000000..9ff01d9
--- /dev/null
+++ b/src/Field/FieldTrait.php
@@ -0,0 +1,154 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+use CodeCoz\AimAdmin\Dto\CrudBoard\FieldDto;
+use function Symfony\Component\String\u;
+
+/**
+ * This is a trait for field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+trait FieldTrait
+{
+ private FieldDto $dto;
+
+ private function __construct()
+ {
+ $this->dto = new FieldDto();
+ }
+
+ public function setName(string $name): self
+ {
+ $this->dto->setName($name);
+
+ return $this;
+ }
+
+ public function setInputType(string $inputType): self
+ {
+ $this->dto->setInputType($inputType);
+
+ return $this;
+ }
+
+ public function setLabel(string|false|null $label): self
+ {
+ $this->dto->setLabel($label ?? self::humanizeString($this->dto->getName()));
+
+ return $this;
+ }
+
+ public function setFormattedValue($value): self
+ {
+ $this->dto->setFormattedValue($value);
+
+ return $this;
+ }
+
+ public function formatValue(?callable $callable): self
+ {
+ $this->dto->setFormatValueCallable($callable);
+
+ return $this;
+ }
+
+ public function setVirtual(bool $isVirtual): self
+ {
+ $this->dto->setVirtual($isVirtual);
+
+ return $this;
+ }
+
+ public function setDisabled(bool $disabled = true): self
+ {
+ $this->dto->setDisable($disabled);
+ return $this;
+ }
+
+ public function setComponent(string $component): self
+ {
+ $this->dto->setComponent($component);
+ return $this;
+ }
+
+ public function setHelp(string $help): self
+ {
+ $this->dto->setHelp($help);
+ return $this;
+ }
+
+ public function addCssClass(string $cssClass): self
+ {
+ $this->dto->setCssClass($this->dto->getCssClass() . ' ' . $cssClass);
+ return $this;
+ }
+
+ public function setCssClass(string $cssClass): self
+ {
+ $this->dto->setCssClass($cssClass);
+ return $this;
+ }
+
+ public function getDto(): FieldDto
+ {
+ return $this->dto;
+ }
+
+ public function setCustomOption(string $optionName, $optionValue): self
+ {
+ $this->dto->setCustomOption($optionName, $optionValue);
+
+ return $this;
+ }
+
+ public function setCustomOptions(array $options): self
+ {
+ $this->dto->setCustomOptions($options);
+
+ return $this;
+ }
+
+ private static function humanizeString(string $string): string
+ {
+ $uString = u($string);
+ $upperString = $uString->upper()->toString();
+
+ // this prevents humanizing all-uppercase labels (e.g. 'UUID' -> 'U u i d')
+ // and other special labels which look better in uppercase
+ if ($uString->toString() === $upperString) {
+ return $upperString;
+ }
+
+ return $uString
+ ->replaceMatches('/([A-Z])/', '_$1')
+ ->replaceMatches('/[_\s]+/', ' ')
+ ->trim()
+ ->lower()
+ ->title(true)
+ ->toString();
+ }
+
+ public function setStyle(string $style): self
+ {
+ $this->dto->setHtmlAttribute('style', $style);
+ return $this;
+ }
+
+ public function addStyle(string $style): self
+ {
+ $this->dto->addHtmlAttributes(['style' => $style]);
+ return $this;
+ }
+
+}
diff --git a/src/Field/FileField.php b/src/Field/FileField.php
new file mode 100644
index 0000000..a4578e1
--- /dev/null
+++ b/src/Field/FileField.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class FileField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, mixed ...$params): self
+ {
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.file')
+ ->setHtmlElementName('input')
+ ->makeFileType()
+ ->setPlaceholder($label ?? self::humanizeString($name))
+ ->setLabel($label ?? self::humanizeString($name));
+ }
+
+}
diff --git a/src/Field/FormField.php b/src/Field/FormField.php
new file mode 100644
index 0000000..03cb85f
--- /dev/null
+++ b/src/Field/FormField.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for field creation in crudboard .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+
+final class FormField implements FieldInterface
+{
+ use FieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params) : self
+ {
+ return (new self())
+ ->setName($name)
+ ->setLabel($label);
+ }
+
+}
diff --git a/src/Field/FormFieldTrait.php b/src/Field/FormFieldTrait.php
new file mode 100644
index 0000000..b6d82c4
--- /dev/null
+++ b/src/Field/FormFieldTrait.php
@@ -0,0 +1,218 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Dto\CrudBoard\FormFieldDto;
+use function Symfony\Component\String\u;
+
+/**
+ * This is a trait for field .
+ *
+ * @author CodeCoz
+ */
+trait FormFieldTrait
+{
+
+ private FormFieldDto $dto;
+
+ private function __construct()
+ {
+ $this->dto = new FormFieldDto();
+ $this->setLayoutClass('col-lg-6');
+ }
+
+ public function setName(string $name): self
+ {
+ $this->dto->setName($name);
+ return $this;
+ }
+
+ public function setHtmlElementName(string $htmlElementName): self
+ {
+ $this->dto->setHtmlElement($htmlElementName);
+ return $this;
+ }
+
+ public function setLabel(string|false|null $label): self
+ {
+ $this->dto->setLabel($label ?? self::humanizeString($this->dto->getName()));
+ return $this;
+ }
+
+ public function setFormattedValue($value): self
+ {
+ $this->dto->setFormattedValue($value);
+ return $this;
+ }
+
+ public function formatValue(?callable $callable): self
+ {
+ $this->dto->setFormatValueCallable($callable);
+ return $this;
+ }
+
+ public function setDisabled(bool $disabled = true): self
+ {
+ $this->dto->setDisable($disabled);
+ return $this;
+ }
+
+ public function setReadonly(bool $readonly = true): self
+ {
+ $this->dto->setReadonly($readonly);
+ return $this;
+ }
+
+ public function setComponent(string $component): self
+ {
+ $this->dto->setComponent($component);
+ return $this;
+ }
+
+ public function setHelp(string $help): self
+ {
+ $this->dto->setHelp($help);
+ return $this;
+ }
+
+ public function addCssClass(string $cssClass): self
+ {
+ $this->dto->setCssClass($this->dto->getCssClass() . ' ' . $cssClass);
+ return $this;
+ }
+
+ public function setCssClass(string $cssClass): self
+ {
+ $this->dto->setCssClass($cssClass);
+ return $this;
+ }
+
+ public function getDto(): FormFieldDto
+ {
+ return $this->dto;
+ }
+
+ public function setCustomOption(string $optionName, $optionValue): self
+ {
+ $this->dto->setCustomOption($optionName, $optionValue);
+ return $this;
+ }
+
+ public function setCustomOptions(array $options): self
+ {
+ $this->dto->setCustomOptions($options);
+ return $this;
+ }
+
+ public function setHtmlAttributes(array $htmlAttributes): self
+ {
+ $this->dto->setHtmlAttributes($htmlAttributes);
+
+ return $this;
+ }
+
+ public function setAttribute(string $name, mixed $value): self
+ {
+ $this->dto->setHtmlAttribute($name, $value);
+ return $this;
+ }
+
+ public function getHtmlAttributes(string|null $attributeName)
+ {
+ return $this->dto->getHtmlAttributes($attributeName);
+ }
+
+
+ private static function humanizeString(string $string): string
+ {
+ $uString = u($string);
+ $upperString = $uString->upper()->toString();
+
+ // this prevents humanizing all-uppercase labels (e.g. 'UUID' -> 'U u i d')
+ // and other special labels which look better in uppercase
+ if ($uString->toString() === $upperString) {
+ return $upperString;
+ }
+
+ return $uString
+ ->replaceMatches('/([A-Z])/', '_$1')
+ ->replaceMatches('/[_\s]+/', ' ')
+ ->trim()
+ ->lower()
+ ->title(true)
+ ->toString();
+ }
+
+ public function setPlaceholder(string $text): self
+ {
+ $this->dto->setHtmlAttribute('placeholder', $text);
+
+ return $this;
+ }
+
+ public function validate(array|string $rules)
+ {
+ $this->dto->setValidationRule($rules);
+ return $this;
+ }
+
+ public function setInputType(string $inputType): self
+ {
+ $this->dto->setInputType($inputType);
+ return $this;
+
+ }
+
+ public function getInputType(): ?string
+ {
+ return $this->dto->getInputType();
+
+ }
+
+ public function setDefaultValue(?string $value = null): self
+ {
+ $this->dto->setValue($value);
+ return $this;
+ }
+
+ public function setLayoutClass(string $class): self
+ {
+ $this->dto->setLayoutClass($class);
+ return $this;
+ }
+
+ public function makeHiddenType(): self
+ {
+ $this->dto->setInputType(FormFieldDto::INPUT_TYPE_HIDDEN);
+ return $this;
+ }
+
+ public function makeFileType(): self
+ {
+ $this->dto->setInputType(FormFieldDto::INPUT_TYPE_FILE);
+ return $this;
+ }
+
+ public function setStyle(string $style): self
+ {
+ $this->dto->setHtmlAttribute('style', $style);
+ return $this;
+ }
+
+ public function addStyle(string $style): self
+ {
+ $this->dto->addHtmlAttributes(['style' => $style]);
+ return $this;
+ }
+
+}
diff --git a/src/Field/GroupActionField.php b/src/Field/GroupActionField.php
new file mode 100644
index 0000000..114eae5
--- /dev/null
+++ b/src/Field/GroupActionField.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+use function Symfony\Component\String\u;
+
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use CodeCoz\AimAdmin\Dto\CrudBoard\ActionDto;
+
+
+/**
+ * This class is for group button creation in crudboard .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class GroupActionField
+{
+ private ActionDto $dto;
+ private ActionCollection $children;
+
+ private function __construct(ActionDto $actionDto)
+ {
+ $this->dto = $actionDto;
+ }
+
+ public function __toString()
+ {
+ return $this->dto->getName();
+ }
+
+ /**
+ * @param mixed $label Use FALSE to hide the label; use NULL to autogenerate it
+ * @param string|null $icon The full CSS classes of the FontAwesome icon to render (see https://fontawesome.com/v6/search?m=free)
+ */
+ public static function init(string $name, ?string $label = null, mixed ...$prarms): self
+ {
+ $dto = new ActionDto();
+ $dto->setName($name);
+ $dto->setLabel($label);
+ $dto->setComponent('aim-admin::crudboard.actions.group');
+ return new self($dto);
+ }
+
+}
diff --git a/src/Field/HiddenField.php b/src/Field/HiddenField.php
new file mode 100644
index 0000000..92d3008
--- /dev/null
+++ b/src/Field/HiddenField.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class HiddenField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params) :self
+ {
+ $value = $params[0] ?? ($params['value'] ?? null);
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.hidden')
+ ->setDefaultValue($value)
+ ;
+ }
+
+
+}
diff --git a/src/Field/IdField.php b/src/Field/IdField.php
new file mode 100644
index 0000000..a65cb08
--- /dev/null
+++ b/src/Field/IdField.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class IdField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, mixed ...$params): self
+ {
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.hidden')
+ ->makeHiddenType()
+ ->setLabel($label ?? self::humanizeString($name));
+ }
+
+
+}
diff --git a/src/Field/InputField.php b/src/Field/InputField.php
new file mode 100644
index 0000000..2c10e1d
--- /dev/null
+++ b/src/Field/InputField.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class InputField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ $type = $params[0] ?? ($params['type'] ?? 'text');
+ $value = $params[1] ?? ($params['value'] ?? null);
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.input')
+ ->setHtmlElementName('input')
+ ->setPlaceholder($label ?? self::humanizeString($name))
+ ->setInputType($type)
+ ->setLabel($label ?? self::humanizeString($name))
+ ->setDefaultValue($value)
+ ;
+ }
+
+
+
+
+}
diff --git a/src/Field/TextField.php b/src/Field/TextField.php
new file mode 100644
index 0000000..b27cf32
--- /dev/null
+++ b/src/Field/TextField.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class TextField implements FieldInterface
+{
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.input')
+ ->setHtmlElementName('input')
+ ->setPlaceholder($label ?? self::humanizeString($name))
+ ->setInputType('text')
+ ->setLabel($label ?? self::humanizeString($name))
+ ->setAttribute('id', $name);
+ }
+
+
+}
diff --git a/src/Field/TextareaField.php b/src/Field/TextareaField.php
new file mode 100644
index 0000000..c27f39c
--- /dev/null
+++ b/src/Field/TextareaField.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Field;
+
+
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+
+
+/**
+ * This class is for creating text field .
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class TextareaField implements FieldInterface
+{
+ public final const ROW = 'rows';
+ public final const COL = 'cols';
+ private static array $defaultParams = [self::ROW => 2, self::COL => null];
+
+ use FormFieldTrait;
+
+ public static function init(string $name, ?string $label = null, ...$params): self
+ {
+ $label = $label ?? self::humanizeString($name);
+ $finalParams = $params + self::$defaultParams;
+ return (new self())
+ ->setName($name)
+ ->setComponent('aim-admin::crudboard.fields.textarea')
+ ->setLabel($label)
+ ->setCustomOption(self::ROW, $finalParams[self::ROW])
+ ->setPlaceholder($label)
+ ->setAttribute('id', $name);
+ }
+
+ public function setNumOfRows(int $rows): self
+ {
+ if ($rows < 1) {
+ throw new \InvalidArgumentException(sprintf('The argument of the "%s()" method must be 1 or higher (%d given).', __METHOD__, $rows));
+ }
+ $this->setCustomOption(self::ROW, $rows);
+ return $this;
+ }
+
+
+}
diff --git a/src/Form/AbstractForm.php b/src/Form/AbstractForm.php
new file mode 100644
index 0000000..59dc420
--- /dev/null
+++ b/src/Form/AbstractForm.php
@@ -0,0 +1,229 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Form;
+
+use ArrayAccess;
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use CodeCoz\AimAdmin\Collection\FormFieldCollection;
+use CodeCoz\AimAdmin\Contracts\Field\FieldInterface;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudFormHandlerInterface;
+use CodeCoz\AimAdmin\Field\IdField;
+
+/**
+ * This is an abstract form class for crud board
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+abstract class AbstractForm
+{
+ const STAT_NEW = 'new';
+ const STAT_EDIT = 'edit';
+ const STAT_NEW_SUBMIT = 'new_submit';
+ const STAT_EDIT_SUBMIT = 'edit_submit';
+ private $formStat;
+ private string $actionUrl;
+ private string $name;
+ private array $attributes = [];
+ private string $cssClass = '';
+ private string $method = 'post';
+ private FormFieldCollection $fields;
+ private array $rawFields;
+
+ private CrudFormHandlerInterface $handler;
+ protected ActionCollection $actions;
+ private $data;
+ private ?string $title = null;
+
+ public function addFields(array $fields)
+ {
+ $this->fields = FormFieldCollection::init($fields);
+ $this->rawFields = $fields;
+ return $this;
+ }
+
+ public function addField($field)
+ {
+ $this->fields->add($field);
+ }
+
+ function getFields(): FormFieldCollection
+ {
+ return $this->fields;
+ }
+
+ public function setFormHandler(CrudFormHandlerInterface $handler)
+ {
+ $this->handler = $handler;
+ return $this;
+ }
+
+ public function saveData(array $data)
+ {
+
+ $this->data = $this->handler->saveFormData($data);
+ return $this;
+ }
+
+ public function setName(string $name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setActionUrl(string $url)
+ {
+ $this->actionUrl = $url;
+ return $this;
+ }
+
+ public function getActionUrl(): string
+ {
+ return $this->actionUrl;
+ }
+
+ public function setCssClass(string $cssClass)
+ {
+ $this->cssClass .= ' ' . $cssClass;
+ return $this;
+ }
+
+ public function getCssClass(): string
+ {
+ return $this->cssClass;
+ }
+
+ public function getAttributesAsHtml(): string
+ {
+ $html = '';
+ foreach ($this->attributes as $formAttr => $val) {
+ $html .= ($formAttr == 'class') ? $this->setCssClass($val) : "$formAttr='$val' ";
+ };
+ return $html;
+ }
+
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = \array_merge($this->attributes, $attributes);
+ return $this;
+ }
+
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ public function setMethod(string $method)
+ {
+ $this->method = $method;
+ return $this;
+ }
+
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+ public function setActions(array $actions): static
+ {
+ $dtos = [];
+ foreach ($actions as $action) {
+ $dto = $action->getDto();
+ $dto->isFormAction() && $dtos[] = $dto;
+ }
+ $this->actions = ActionCollection::init($dtos);
+ return $this;
+ }
+
+
+ public function setData(\ArrayAccess $data): static
+ {
+ foreach ($this->fields as $name => &$dto) {
+ $dto->setValue($data[$name]);
+ }
+ return $this;
+ }
+
+ public function setFields(array $fieldUpdates): static
+ {
+ $updatedFields = array_reduce($this->rawFields, function ($carry, $field) {
+ $carry[$field->getDto()->getName()] = $field;
+ return $carry;
+ }, []);
+
+ foreach ($fieldUpdates as $name => $updateAction) {
+ if (isset($updatedFields[$name])) {
+ if (is_callable($updateAction)) {
+ $updatedFields[$name] = $updateAction($updatedFields[$name]);
+ } elseif ($updateAction instanceof FieldInterface) {
+ // This allows replacing the field with a new type
+ $updatedFields[$name] = $updateAction;
+ }
+ } elseif ($updateAction instanceof FieldInterface) {
+ $updatedFields[$name] = $updateAction;
+ } else {
+ throw new \InvalidArgumentException("Invalid update action for field '$name'");
+ }
+ }
+
+ $this->addFields(array_values($updatedFields));
+
+ return $this;
+ }
+
+
+ public function addIdField(string $name = 'id'): static
+ {
+ $field = IdField::init($name);
+ $this->addField($field);
+ return $this;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ public function setFormStat(string $stat): static
+ {
+ $this->formStat = $stat;
+ return $this;
+ }
+
+ public function isNew()
+ {
+ return (self::STAT_NEW === $this->formStat);
+ }
+
+
+ public function isEdit()
+ {
+ return (self::STAT_EDIT === $this->formStat);
+ }
+
+ public function setTitle(string $title): self
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ public function getTitle(): ?string
+ {
+ return $this->title;
+ }
+
+
+}
diff --git a/src/Form/CrudForm.php b/src/Form/CrudForm.php
new file mode 100644
index 0000000..2daaf73
--- /dev/null
+++ b/src/Form/CrudForm.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Form;
+
+use Illuminate\Http\Request;
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+
+
+/**
+ * This is an CRUD form class in crud board. Main purpose of this form is to build form to create DB object
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+class CrudForm extends AbstractForm
+{
+ private Request $request;
+
+
+ public function getActions(): ActionCollection
+ {
+ return $this->actions->getFormActions();
+ }
+
+
+ public function processData(Request $request)
+ {
+ $this->request = $request;
+ $this->validate();
+ return $this->save();
+ }
+
+ private function validate()
+ {
+ $rules = $this->prepareValidationRule();
+ $this->request->validate($rules);
+ }
+
+ private function prepareValidationRule(): array
+ {
+ $rules = [];
+ foreach ($this->getFields() as $field) {
+ $field->getValidationRule() && $rules[$field->getName()] = $field->getValidationRule();
+ }
+ return $rules;
+ }
+
+ private function save()
+ {
+ $data = [];
+ foreach ($this->getFields() as $field) {
+ $data[$field->getName()] = $this->request->input($field->getName());
+ }
+ return $this->saveData($data);
+ }
+}
diff --git a/src/Guard/AimGuard.php b/src/Guard/AimGuard.php
new file mode 100644
index 0000000..73afcca
--- /dev/null
+++ b/src/Guard/AimGuard.php
@@ -0,0 +1,93 @@
+provider = $provider;
+ $this->request = $request;
+ $this->session = $session;
+ }
+
+ public function check(): bool
+ {
+ return !is_null($this->user());
+ }
+
+ public function guest(): bool
+ {
+ return !$this->check();
+ }
+
+ public function user()
+ {
+ if (isset($this->user)) {
+ return $this->user;
+ }
+
+ $user = $this->session->get('user');
+
+ if (!is_null($user)) {
+ $this->user = $this->provider->retrieveById($user['id']);
+ return $this->user;
+ }
+
+ return null;
+ }
+
+
+ public function id()
+ {
+ if ($this->user()) {
+ return $this->user->getAuthIdentifier();
+ }
+ return null;
+ }
+
+ public function validate(array $credentials = [])
+ {
+ $user = $this->provider->retrieveByCredentials($credentials);
+
+ if ($user && $this->provider->validateCredentials($user, $credentials)) {
+ $this->session->put('user_id', $user->getAuthIdentifier());
+ $this->setUser($user);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function setUser(Authenticatable $user)
+ {
+ $this->user = $user;
+ return $this;
+ }
+
+ public function hasUser()
+ {
+ if ($this->user()) {
+ return true;
+ }
+ return false;
+ }
+
+ public function logout()
+ {
+ $this->user = null;
+ $this->session->remove('user');
+ $this->session->remove('user_id');
+ }
+}
diff --git a/src/Helpers/Helper.php b/src/Helpers/Helper.php
new file mode 100644
index 0000000..2b80c43
--- /dev/null
+++ b/src/Helpers/Helper.php
@@ -0,0 +1,1474 @@
+attributes->get($key);
+ }
+
+ static function setAttribute($key, $value): void
+ {
+ request()->attributes->set($key, $value);
+ }
+
+ static function requiredPackages(): array
+ {
+ return [
+ "@fortawesome/fontawesome-free" => "~6.5.2",
+ "admin-lte" => "~3.2.0",
+ "chart.js" => "~4.4.3",
+ "jquery" => "~3.7.1",
+ "flatpickr" => "~4.6.13",
+ "sweetalert2" => "~11.11.0",
+ "jstree" => "~3.3.16",
+ "laravel-vite-plugin" => "~1.0.4",
+ "vite-plugin-static-copy" => "~1.0.5",
+ "sass" => "~1.77.2",
+ "vite" => "~5.2.11"
+ ];
+ }
+
+ static function badge($badge, $class = 'bg-info'): string
+ {
+ return '' . $badge . '';
+ }
+
+ static function formatPhoneNumber($phone): string
+ {
+ // Remove any non-numeric characters
+ $phone = preg_replace('/\D/', '', $phone);
+ // Ensure the remaining string is numeric
+ if (!is_numeric($phone)) {
+ return "NO";
+ }
+ // Check if the phone contains a valid prefix followed by 8 digits
+ $pattern = '/.*(1[3-9]\d{8}).*/';
+ if (preg_match($pattern, $phone, $matches)) {
+ // Return the formatted number with "880" prepended
+ return "880" . $matches[1];
+ }
+ return "NO";
+ }
+
+}
+
diff --git a/src/Http/Controllers/Auth/LoginController.php b/src/Http/Controllers/Auth/LoginController.php
new file mode 100644
index 0000000..0b0e88a
--- /dev/null
+++ b/src/Http/Controllers/Auth/LoginController.php
@@ -0,0 +1,46 @@
+validated();
+
+ if (Auth::attempt($credentials)) {
+ $request->session()->regenerate();
+
+ return redirect()->intended('dashboard');
+ }
+
+ return back()->withErrors([
+ 'email' => 'The provided credentials do not match our records.',
+ ])->onlyInput('email');
+ }
+
+ public function logout(Request $request)
+ {
+ Auth::logout();
+
+ $request->session()->invalidate();
+
+ $request->session()->regenerateToken();
+
+ return $request->wantsJson()
+ ? new JsonResponse([], 204)
+ : redirect('/');
+ }
+}
diff --git a/src/Http/Controllers/Auth/RegistrationController.php b/src/Http/Controllers/Auth/RegistrationController.php
new file mode 100644
index 0000000..7d6cbe1
--- /dev/null
+++ b/src/Http/Controllers/Auth/RegistrationController.php
@@ -0,0 +1,38 @@
+validated();
+
+ $user = User::create([
+ 'name' => $credentials['name'],
+ 'email' => $credentials['email'],
+ 'password' => Hash::make($credentials['password']),
+ ]);
+
+ if ($user) {
+ Auth::login($user);
+ return redirect()->intended('dashboard');
+ }
+
+ return back()->withErrors([
+ 'email' => 'The provided credentials do not match our records.',
+ ])->onlyInput('email');
+ }
+}
diff --git a/src/Http/Requests/AuthenticateRequest.php b/src/Http/Requests/AuthenticateRequest.php
new file mode 100644
index 0000000..a38d9f7
--- /dev/null
+++ b/src/Http/Requests/AuthenticateRequest.php
@@ -0,0 +1,29 @@
+|string>
+ */
+ public function rules(): array
+ {
+ return [
+ 'email' => ['required', 'email'],
+ 'password' => ['required'],
+ ];
+ }
+}
diff --git a/src/Http/Requests/RegisterRequest.php b/src/Http/Requests/RegisterRequest.php
new file mode 100644
index 0000000..d215576
--- /dev/null
+++ b/src/Http/Requests/RegisterRequest.php
@@ -0,0 +1,31 @@
+|string>
+ */
+ public function rules(): array
+ {
+ return [
+ 'name' => ['required', 'max:254'],
+ 'email' => ['required', Rule::unique('users')],
+ 'password' => ['required', 'string', 'min:6', 'confirmed']
+ ];
+ }
+}
diff --git a/src/MenuBuilder/ActiveChecker.php b/src/MenuBuilder/ActiveChecker.php
new file mode 100644
index 0000000..08be7ca
--- /dev/null
+++ b/src/MenuBuilder/ActiveChecker.php
@@ -0,0 +1,147 @@
+request = $url->getRequest();
+ $this->url = $url;
+
+ // Fill the map with tests. These tests will check if a menu item is
+ // active or not.
+
+ $this->tests = [
+ 'submenu' => [$this, 'containsActive'],
+ 'active' => [$this, 'isExplicitActive'],
+ 'href' => [$this, 'checkPattern'],
+ 'url' => [$this, 'checkPattern'],
+ ];
+ }
+
+ /**
+ * Checks if a menu item is currently active. Active items will be
+ * highlighted.
+ *
+ * @param mixed $item The menu item to check
+ * @return bool
+ */
+ public function isActive($item): bool
+ {
+ // Return true if any of the verification tests is met.
+
+ foreach ($this->tests as $prop => $testFunc) {
+ if (isset($item[$prop]) && $testFunc($item[$prop])) {
+ return true;
+ }
+ }
+
+ // Otherwise, returns false.
+
+ return false;
+ }
+
+ /**
+ * Checks if an array of items contains an active item.
+ *
+ * @param array $items The items to check
+ * @return bool
+ */
+ protected function containsActive($items): bool
+ {
+ foreach ($items as $item) {
+ if ($this->isActive($item)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if an item is active by explicit definition of 'active' state.
+ *
+ * @param bool|array $activeDef
+ * @return bool
+ */
+ protected function isExplicitActive($activeDef)
+ {
+ // If the active definition is a bool, return it.
+
+ if (is_bool($activeDef)) {
+ return $activeDef;
+ }
+
+ // Otherwise, check if any of the url patterns that defines the active
+ // state matches the requested url.
+
+ foreach ($activeDef as $pattern) {
+ if ($this->checkPattern($pattern)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if an url pattern matches the requested url.
+ *
+ * @param string $pattern
+ * @return bool
+ */
+ protected function checkPattern($pattern)
+ {
+ // First, check if the pattern is a regular expression.
+
+ if (Str::startsWith($pattern, 'regex:')) {
+ $regex = Str::substr($pattern, 6);
+
+ return (bool)preg_match($regex, $this->request->path());
+ }
+
+ // If pattern is not a regex, check if the requested url matches the
+ // absolute path to the given pattern. When the pattern uses query
+ // parameters, compare with the full url request.
+
+ $pattern = preg_replace('@^https?://@', '*', $this->url->to($pattern));
+ $request = $this->request->url();
+
+ if (isset(parse_url($pattern)['query'])) {
+ $request = $this->request->fullUrl();
+ }
+
+ return Str::is(trim($pattern), trim($request));
+ }
+}
diff --git a/src/MenuBuilder/AimAdminMenu.php b/src/MenuBuilder/AimAdminMenu.php
new file mode 100644
index 0000000..8be8687
--- /dev/null
+++ b/src/MenuBuilder/AimAdminMenu.php
@@ -0,0 +1,135 @@
+filters = $filters;
+ $this->container = $container;
+ $this->events = $events;
+
+ // Fill the map with filters methods.
+
+ $this->menuFilterMap = [
+ 'sidebar' => [$this, 'sidebarFilter'],
+ ];
+ }
+
+ /**
+ * Get all the menu items, or a specific set of these.
+ *
+ * @param string $filterToken Token representing a subset of the menu items
+ * @return array A set of menu items
+ */
+ public function menu($filterToken = null)
+ {
+ if (empty($this->menu)) {
+ $this->menu = $this->buildMenu();
+ }
+
+ // Check for filter token.
+
+ if (isset($this->menuFilterMap[$filterToken])) {
+ return array_filter(
+ $this->menu,
+ $this->menuFilterMap[$filterToken]
+ );
+ }
+
+ // No filter token provided, return the complete menu.
+
+ return $this->menu;
+ }
+
+ /**
+ * Build the menu.
+ *
+ * @return array The set of menu items
+ */
+ protected function buildMenu()
+ {
+ // Create the menu builder instance.
+
+ $builder = new Builder($this->buildFilters());
+
+ // Dispatch the BuildingMenu event. Listeners of this event will fill
+ // the menu.
+
+ $this->events->dispatch(new BuildingMenu($builder));
+
+ // Return the set of menu items.
+
+ return $builder->menu;
+ }
+
+ /**
+ * Build the menu filters.
+ *
+ * @return array The set of filters that will apply on each menu item
+ */
+ protected function buildFilters()
+ {
+ return array_map([$this->container, 'make'], $this->filters);
+ }
+
+ /**
+ * Filter method used to get the sidebar menu items.
+ *
+ * @param mixed $item A menu item
+ * @return bool
+ */
+ private function sidebarFilter($item)
+ {
+ return SidebarItemHelper::isValidItem($item);
+ }
+
+}
diff --git a/src/MenuBuilder/Builder.php b/src/MenuBuilder/Builder.php
new file mode 100644
index 0000000..65fa704
--- /dev/null
+++ b/src/MenuBuilder/Builder.php
@@ -0,0 +1,241 @@
+filters = $filters;
+ }
+
+ /**
+ * Add new items at the end of the menu.
+ *
+ * @param mixed $newItems Items to be added
+ */
+ public function add(...$newItems)
+ {
+ $items = $this->transformItems($newItems);
+
+ if (!empty($items)) {
+ foreach ($items as $item) {
+ $this->menu[] = $item;
+ }
+ }
+ }
+
+
+ /**
+ * Add new items after a specific menu item.
+ *
+ * @param mixed $itemKey The key that represents the specific menu item
+ * @param mixed $newItems Items to be added
+ */
+ public function addAfter($itemKey, ...$newItems)
+ {
+ $this->addItem($itemKey, self::ADD_AFTER, ...$newItems);
+ }
+
+ /**
+ * Add new items before a specific menu item.
+ *
+ * @param mixed $itemKey The key that represents the specific menu item
+ * @param mixed $newItems Items to be added
+ */
+ public function addBefore($itemKey, ...$newItems)
+ {
+ $this->addItem($itemKey, self::ADD_BEFORE, ...$newItems);
+ }
+
+ /**
+ * Add new submenu items inside a specific menu item.
+ *
+ * @param mixed $itemKey The key that represents the specific menu item
+ * @param mixed $newItems Items to be added
+ */
+ public function addIn($itemKey, ...$newItems)
+ {
+ $this->addItem($itemKey, self::ADD_INSIDE, ...$newItems);
+ }
+
+ /**
+ * Remove a specific menu item.
+ *
+ * @param mixed $itemKey The key of the menu item to remove
+ */
+ public function remove($itemKey)
+ {
+ // Find the specific menu item. Return if not found.
+
+ if (!($itemPath = $this->findItem($itemKey, $this->menu))) {
+ return;
+ }
+
+ // Remove the item.
+
+ Arr::forget($this->menu, implode('.', $itemPath));
+
+ // Normalize the menu (remove holes in the numeric indexes).
+
+ $holedArrPath = implode('.', array_slice($itemPath, 0, -1)) ?: null;
+ $holedArr = Arr::get($this->menu, $holedArrPath, $this->menu);
+ Arr::set($this->menu, $holedArrPath, array_values($holedArr));
+ }
+
+ /**
+ * Check if exists a menu item with the specified key.
+ *
+ * @param mixed $itemKey The key of the menu item to check for
+ * @return bool
+ */
+ public function itemKeyExists($itemKey)
+ {
+ return (bool)$this->findItem($itemKey, $this->menu);
+ }
+
+ /**
+ * Transform the items by applying the filters.
+ *
+ * @param array $items An array with items to be transformed
+ * @return array Array with the new transformed items
+ */
+ protected function transformItems($items)
+ {
+ return array_filter(
+ array_map([$this, 'applyFilters'], $items),
+ [MenuItemHelper::class, 'isAllowed']
+ );
+ }
+
+ /**
+ * Find a menu item by the item key and return the path to it.
+ *
+ * @param mixed $itemKey The key of the item to find
+ * @param array $items The array to look up for the item
+ * @return mixed Array with the path sequence, or empty array if not found
+ */
+ protected function findItem($itemKey, $items)
+ {
+ // Look up on all the items.
+
+ foreach ($items as $key => $item) {
+ if (isset($item['key']) && $item['key'] === $itemKey) {
+ return [$key];
+ } elseif (MenuItemHelper::isSubmenu($item)) {
+ // Do the recursive call to search on submenu. If we found the
+ // item, merge the path with the current one.
+
+ if ($subPath = $this->findItem($itemKey, $item['submenu'])) {
+ return array_merge([$key, 'submenu'], $subPath);
+ }
+ }
+ }
+
+ // Return empty array when the item is not found.
+
+ return [];
+ }
+
+ /**
+ * Apply all the available filters to a menu item.
+ *
+ * @param mixed $item A menu item
+ * @return mixed A new item with all the filters applied
+ */
+ protected function applyFilters($item)
+ {
+ // Filters are only applied to array type menu items.
+
+ if (!is_array($item)) {
+ return $item;
+ }
+
+ // If the item is a submenu, transform all the submenu items first.
+ // These items need to be transformed first because some of the submenu
+ // filters (like the ActiveFilter) depends on these results.
+
+ if (MenuItemHelper::isSubmenu($item)) {
+ $item['submenu'] = $this->transformItems($item['submenu']);
+ }
+
+ // Now, apply all the filters on the item.
+
+ foreach ($this->filters as $filter) {
+ // If the item is not allowed to be shown, there is no sense to
+ // continue applying the filters.
+
+ if (!MenuItemHelper::isAllowed($item)) {
+ return $item;
+ }
+
+ $item = $filter->transform($item);
+ }
+
+ return $item;
+ }
+
+ /**
+ * Add new items to the menu in a particular place, relative to a
+ * specific menu item.
+ *
+ * @param mixed $itemKey The key that represents the specific menu item
+ * @param int $where Where to add the new items
+ * @param mixed $items Items to be added
+ */
+ protected function addItem($itemKey, $where, ...$items)
+ {
+ // Find the specific menu item. Return if not found.
+
+ if (!($itemPath = $this->findItem($itemKey, $this->menu))) {
+ return;
+ }
+
+ // Get the target array and add the new items there.
+
+ $itemKeyIdx = end($itemPath);
+ reset($itemPath);
+
+ if ($where === self::ADD_INSIDE) {
+ $targetPath = implode('.', array_merge($itemPath, ['submenu']));
+ $targetArr = Arr::get($this->menu, $targetPath, []);
+ array_push($targetArr, ...$items);
+ } else {
+ $targetPath = implode('.', array_slice($itemPath, 0, -1)) ?: null;
+ $targetArr = Arr::get($this->menu, $targetPath, $this->menu);
+ $offset = ($where === self::ADD_AFTER) ? 1 : 0;
+ array_splice($targetArr, $itemKeyIdx + $offset, 0, $items);
+ }
+
+ Arr::set($this->menu, $targetPath, $targetArr);
+
+ // Apply the filters because the menu now have new items.
+
+ $this->menu = $this->transformItems($this->menu);
+ }
+}
diff --git a/src/MenuBuilder/BuildingMenu.php b/src/MenuBuilder/BuildingMenu.php
new file mode 100644
index 0000000..6b5e3f0
--- /dev/null
+++ b/src/MenuBuilder/BuildingMenu.php
@@ -0,0 +1,23 @@
+menu = $menu;
+ }
+}
diff --git a/src/MenuBuilder/Filters/ActiveFilter.php b/src/MenuBuilder/Filters/ActiveFilter.php
new file mode 100644
index 0000000..895d8a1
--- /dev/null
+++ b/src/MenuBuilder/Filters/ActiveFilter.php
@@ -0,0 +1,38 @@
+activeChecker = $activeChecker;
+ }
+
+ /**
+ * Transforms a menu item. Adds the active attribute when suitable.
+ *
+ * @param array $item A menu item
+ * @return array The transformed menu item
+ */
+ public function transform($item): array
+ {
+ $item['active'] = $this->activeChecker->isActive($item);
+
+ return $item;
+ }
+}
diff --git a/src/MenuBuilder/Filters/ClassesFilter.php b/src/MenuBuilder/Filters/ClassesFilter.php
new file mode 100644
index 0000000..b49356b
--- /dev/null
+++ b/src/MenuBuilder/Filters/ClassesFilter.php
@@ -0,0 +1,72 @@
+makeClasses($item));
+
+ if (MenuItemHelper::isSubmenu($item)) {
+ $item['submenu_class'] = implode(' ', $this->makeSubmenuClasses($item));
+ }
+
+ return $item;
+ }
+
+ /**
+ * Make classes related to the components of a menu item.
+ *
+ * @param array $item A menu item
+ * @return array The array of classes
+ */
+ protected function makeClasses($item)
+ {
+ $classes = [];
+
+ // Add custom classes (from menu item configuration).
+
+ if (!empty($item['classes'])) {
+ $classes[] = $item['classes'];
+ }
+
+ // Add the active class when the item is active.
+
+ if (!empty($item['active'])) {
+ $classes[] = 'active';
+ }
+
+ return $classes;
+ }
+
+ /**
+ * Make classes related to the components of a submenu item.
+ *
+ * @param array $item A menu item
+ * @return array The array of classes
+ */
+ protected function makeSubmenuClasses($item)
+ {
+ $classes = [];
+
+ // Add the menu-open class when a sidebar submenu is active. Note we
+ // need to add the class to sidebar submenu items only.
+
+ if (SidebarItemHelper::isValidItem($item) && $item['active']) {
+ $classes[] = 'menu-open';
+ }
+
+ return $classes;
+ }
+}
diff --git a/src/MenuBuilder/Filters/FilterInterface.php b/src/MenuBuilder/Filters/FilterInterface.php
new file mode 100644
index 0000000..38e7e0c
--- /dev/null
+++ b/src/MenuBuilder/Filters/FilterInterface.php
@@ -0,0 +1,14 @@
+urlGenerator = $urlGenerator;
+ }
+
+ /**
+ * Transforms a menu item. Make the href attribute when situable.
+ *
+ * @param array $item A menu item
+ * @return array The transformed menu item
+ */
+ public function transform($item)
+ {
+ if (!MenuItemHelper::isHeader($item)) {
+ $item['href'] = $this->makeHref($item);
+ }
+
+ return $item;
+ }
+
+ /**
+ * Make the href attribute for a menu item.
+ *
+ * @param array $item A menu item
+ * @return string The href attribute
+ */
+ protected function makeHref($item)
+ {
+ // If url attribute is available, use it to make the href.
+
+ if (isset($item['url'])) {
+ return $this->urlGenerator->to($item['url']);
+ }
+
+ // When url is not available, check for route attribute.
+
+ if (isset($item['route'])) {
+ if (is_array($item['route'])) {
+ $route = $item['route'][0];
+ $params = is_array($item['route'][1]) ? $item['route'][1] : [];
+
+ return $this->urlGenerator->route($route, $params);
+ }
+
+ return $this->urlGenerator->route($item['route']);
+ }
+
+ // When no url or route, return a default value.
+
+ return '#';
+ }
+}
diff --git a/src/MenuBuilder/MenuItemHelper.php b/src/MenuBuilder/MenuItemHelper.php
new file mode 100644
index 0000000..6ba9cd8
--- /dev/null
+++ b/src/MenuBuilder/MenuItemHelper.php
@@ -0,0 +1,59 @@
+
+ */
+ protected $hidden = [
+ 'password',
+ 'remember_token',
+ ];
+
+
+ /**
+ * Get the attributes that should be cast.
+ *
+ * @return array
+ */
+ protected function casts(): array
+ {
+ return [
+ 'email_verified_at' => 'datetime',
+ 'password' => 'hashed',
+ ];
+ }
+
+ public function userImage(): ?string
+ {
+ return "";
+ }
+
+}
diff --git a/src/Providers/MenuServiceProvider.php b/src/Providers/MenuServiceProvider.php
new file mode 100644
index 0000000..47e099e
--- /dev/null
+++ b/src/Providers/MenuServiceProvider.php
@@ -0,0 +1,63 @@
+app['config']['aim-admin'])) {
+ // Merge the package's default configuration if it doesn't exist
+ $this->mergeConfigFrom(
+ Admin::packagePath('config/aim-admin.php'), 'aim-admin'
+ );
+ }
+
+ // Register the service as a singleton
+ $this->app->singleton(AimAdminMenu::class, function ($app) {
+ $config = $app['config']->get('aim-admin.menu_filters', []);
+ $events = $app['events'];
+
+ return new AimAdminMenu($config, $events, $app);
+ });
+ }
+
+
+ /**
+ * Bootstrap the package's services.
+ *
+ * @return void
+ */
+ public function boot(Dispatcher $events, Repository $config)
+ {
+ $this->registerMenu($events, $config);
+ }
+
+ /**
+ * Register the menu events handlers.
+ *
+ * @return void
+ */
+ private function registerMenu(Dispatcher $events, Repository $config)
+ {
+ $events->listen(BuildingMenu::class, function (BuildingMenu $event) use ($config) {
+ $menu = $config->get('aim-admin.menu', []);
+ $menu = is_array($menu) ? $menu : [];
+ $event->menu->add(...$menu);
+ });
+ }
+
+}
diff --git a/src/Providers/RepositoryServiceProvider.php b/src/Providers/RepositoryServiceProvider.php
new file mode 100644
index 0000000..c003054
--- /dev/null
+++ b/src/Providers/RepositoryServiceProvider.php
@@ -0,0 +1,83 @@
+isDirectory(app_path('Contracts/Services'))) {
+ $this->autoBindInterfaces(app_path('Contracts/Services'), app_path('Services'));
+ }
+ if ($filesystem->isDirectory(app_path('Contracts/Repositories'))) {
+ $this->autoBindInterfaces(app_path('Contracts/Repositories'), app_path('Repositories'));
+ }
+ });
+ }
+
+
+ /**
+ * @param $contractsPath
+ * @param $implementationPath
+ * @return void
+ */
+ private function autoBindInterfaces($contractsPath, $implementationPath): void
+ {
+ $files = app(Filesystem::class)->allFiles($contractsPath);
+
+ foreach ($files as $file) {
+
+ $contractNamespace = $this->extractNamespace($file->getPathname());
+
+ $contractClass = $contractNamespace . "\\" . $file->getFilenameWithoutExtension();
+
+ // Ensure class existence and interface nature
+ if (!interface_exists($contractClass)) {
+ continue; // Skip if not a valid interface
+ }
+
+ $implementationClass = $this->getImplementationClass($contractClass);
+
+ if (class_exists($implementationClass)) {
+ $this->app->bind($contractClass, $implementationClass);
+ }
+
+ }
+ }
+
+ private function getImplementationClass($contractClass): array|string|null
+ {
+ // Remove the 'Contracts' namespace segment and 'Interface' suffix
+ $implementationClass = str_replace('Contracts\\', '', $contractClass);
+ return preg_replace('/Interface$/', '', $implementationClass);
+ }
+
+ private function extractNamespace(string $filePath): string
+ {
+ // Optimized namespace extraction using a single regular expression
+ $contents = file_get_contents($filePath);
+ preg_match('/namespace\s+(.*);/', $contents, $matches);
+ return trim($matches[1]) ?? '';
+ }
+
+ /**
+ * Bootstrap services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ //
+ }
+}
diff --git a/src/Providers/UserServiceProvider.php b/src/Providers/UserServiceProvider.php
new file mode 100644
index 0000000..e3c8264
--- /dev/null
+++ b/src/Providers/UserServiceProvider.php
@@ -0,0 +1,71 @@
+registerUserProviderDriver();
+ $this->registerGuard();
+ }
+
+ /**
+ * Register the User Provider handlers.
+ *
+ * @return void
+ */
+ private function registerUserProviderDriver(): void
+ {
+ Auth::provider('AimAdminUserProviderDriver', function ($app, array $config) {
+ return new AimAdminUserProviderDriver();
+ });
+
+// Overriding the default users driver
+ config([
+ 'auth.providers.users.driver' => env('USER_PROVIDER_DRIVER', 'AimAdminUserProviderDriver')
+ ]);
+ }
+
+ /**
+ * Register the Guard handlers.
+ *
+ * @return void
+ */
+ private function registerGuard(): void
+ {
+ Auth::extend('AimAdminGuard', function ($app, $config) {
+ return new AimGuard(
+ Auth::createUserProvider('users'),
+ $app['request'],
+ $app['session.store']);
+ });
+
+// Overriding the default guard configuration
+ config([
+ 'auth.guards.web.driver' => env('DEFAULT_GUARD', 'AimAdminGuard')
+ ]);
+ }
+
+}
diff --git a/src/Providers/ViewServiceProvider.php b/src/Providers/ViewServiceProvider.php
new file mode 100644
index 0000000..34c3885
--- /dev/null
+++ b/src/Providers/ViewServiceProvider.php
@@ -0,0 +1,39 @@
+exists($file)) {
+ $content .= view($file)->render();
+ } else {
+ Log::warning("JS file not found: {$file}"); // Log if file is missing
+ }
+ }
+ return $this->with('injectedTop', $content);
+ });
+
+ // Macro for injecting CSS
+ View::macro('injectBottom', function ($files) {
+ $content = '';
+ foreach ((array)$files as $file) {
+ if (view()->exists($file)) {
+ $content .= view($file)->render();
+ } else {
+ Log::warning("CSS file not found: {$file}"); // Log if file is missing
+ }
+ }
+ return $this->with('injectedBottom', $content);
+ });
+ }
+}
diff --git a/src/Repository/AbstractAimAdminRepository.php b/src/Repository/AbstractAimAdminRepository.php
new file mode 100644
index 0000000..c4b886a
--- /dev/null
+++ b/src/Repository/AbstractAimAdminRepository.php
@@ -0,0 +1,120 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Repository;
+
+use CodeCoz\AimAdmin\Contracts\Repository\AimAdminRepositoryInterface;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudFormHandlerInterface;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudGridLoaderInterface;
+use Illuminate\Pagination\CursorPaginator;
+use Illuminate\Pagination\Paginator;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Collection;
+
+/**
+ * This interface defines blueprints of AimAdmin Repository.
+ *
+ *
+ * @author CodeCoz
+ */
+abstract class AbstractAimAdminRepository implements AimAdminRepositoryInterface, CrudFormHandlerInterface, CrudGridLoaderInterface
+{
+ public function all(): iterable
+ {
+ return $this->getModelFqcn()::all();
+ }
+
+ public function findOrFail(int|string $id): object
+ {
+ return $this->getModelFqcn()::findOrFail($id);
+ }
+
+ public function find($id)
+ {
+ return $this->getModelFqcn()::find($id);
+ }
+
+ public function findOneBy(string $field, $value)
+ {
+ return $this->getModelFqcn()::where($field, $value)->first();
+ }
+
+ protected function findBy($field, $value): iterable
+ {
+ return $this->getModelFqcn()::where($field, $value)->get();
+ }
+
+ public function saveFormData(array $data = [])
+ {
+ $class = $this->getModelFqcn();
+ $model = new $class;
+ foreach ($data as $key => $value) {
+ $model->$key = $value;
+ }
+ $model->save();
+ return $model;
+ }
+
+ public function crudShow(int|string $id): ?\ArrayAccess
+ {
+ return $this->find($id);
+ }
+
+ public function getRecordForEdit(int|string $id): object
+ {
+ $record = $this->findOrFail($id);
+ return $record;
+ }
+
+ public function getGridData(array $filters = []): ?iterable
+ {
+ return null;
+ }
+
+ public function getGridQuery(): ?Builder
+ {
+ return null;
+ }
+
+ public function getGridPagination(): ?Paginator
+ {
+ return null;
+ }
+
+ public function getGridCursorPaginator(array $filters): ?CursorPaginator
+ {
+ return null;
+ }
+
+ public function getGridPaginator(array $filters): ?LengthAwarePaginator
+ {
+ return null;
+ }
+
+ public function applyFilterQuery(Builder $query, array $filters): Builder
+ {
+ foreach ($filters as $field => $value) {
+ $query->where($field, $value);
+ }
+ return $query;
+ }
+
+ public function applyFilterData(Collection $data, array $filters): Collection
+ {
+ foreach ($filters as $field => $value) {
+ $filtered = $data->where($field, $value);
+ $data = $filtered;
+ }
+ return $data;
+ }
+
+}
diff --git a/src/Services/CrudBoard/AbstractCrudBoard.php b/src/Services/CrudBoard/AbstractCrudBoard.php
new file mode 100644
index 0000000..f964d61
--- /dev/null
+++ b/src/Services/CrudBoard/AbstractCrudBoard.php
@@ -0,0 +1,88 @@
+grid = CrudGrid::init($dataLoader, $params);
+ return $this->grid;
+ }
+
+ public function getRepository(): AimAdminRepositoryInterface
+ {
+ return $this->repo;
+ }
+
+ public function getGrid(): CrudGridInterface
+ {
+ return $this->grid;
+ }
+
+ public function addGridColumns(array $columns)
+ {
+ $this->grid->addColumns($columns);
+ return $this;
+ }
+
+ public function getForm(): AbstractForm
+ {
+ return $this->form;
+ }
+
+
+ public function addGridActions(array $actions = [])
+ {
+ $this->grid->addActions($actions);
+ return $this;
+ }
+
+ protected function defaultGridRowActions()
+ {
+
+ }
+
+ public function createForm(array $fields): CrudForm
+ {
+ $this->form = (new CrudForm())
+ ->addFields($fields);
+ return $this->form;
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function createShow(mixed $row, array $fields)
+ {
+ $record = ($row instanceof Model) ? $row :$this->getRecordForShow($row);
+ if ($record === null) {
+ throw new \Exception("No record is found for details");
+ }
+ $this->crudShow = CrudShow::init($fields, $record);
+ return $this->crudShow;
+ }
+
+ public function getCrudShow(): CrudShowInterface
+ {
+ return $this->crudShow;
+ }
+
+}
diff --git a/src/Services/CrudBoard/CrudBoard.php b/src/Services/CrudBoard/CrudBoard.php
new file mode 100644
index 0000000..4074243
--- /dev/null
+++ b/src/Services/CrudBoard/CrudBoard.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Services\CrudBoard;
+
+
+/**
+ * This interface defines blueprints of AimAdmin CRUD grid loader.
+ * It will ensure to provide record for CrudBoard grid.
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+
+ use CodeCoz\AimAdmin\Contracts\Repository\AimAdminRepositoryInterface;
+
+final class CrudBoard extends AbstractCrudBoard
+{
+ private $params = [];
+ public function setParam($param)
+ {
+ $this->params[] = $param;
+ }
+
+ public function getParams() : array
+ {
+ return $this->params;
+ }
+
+ public function setRepository(AimAdminRepositoryInterface $repo) : self
+ {
+ $this->repo = $repo;
+ return $this;
+ }
+
+ protected function getRecordForShow(int|string $id): ?\ArrayAccess
+ {
+ return $this->getRepository()->crudShow($id);
+ }
+}
diff --git a/src/Services/CrudBoard/CrudForm.php b/src/Services/CrudBoard/CrudForm.php
new file mode 100644
index 0000000..3543f7b
--- /dev/null
+++ b/src/Services/CrudBoard/CrudForm.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Services\CrudBoard;
+
+use Illuminate\Http\Request;
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use CodeCoz\AimAdmin\Form\AbstractForm;
+
+/**
+ * This is an CRUD form class in crud board. Main purpose of this form is to build form to create DB object
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+class CrudForm extends AbstractForm
+{
+ private Request $request;
+
+
+ public function getActions(): ActionCollection
+ {
+ return $this->actions->getFormActions();
+ }
+
+
+ public function processData(Request $request)
+ {
+ $this->request = $request;
+ $this->validate();
+ return $this->save();
+ }
+
+ private function validate()
+ {
+ $rules = $this->prepareValidationRule();
+ $this->request->validate($rules);
+ }
+
+ private function prepareValidationRule(): array
+ {
+ $rules = [];
+ foreach ($this->getFields() as $field) {
+ $field->getValidationRule() && $rules[$field->getName()] = $field->getValidationRule();
+ }
+ return $rules;
+ }
+
+ private function save()
+ {
+ $data = [];
+ foreach ($this->getFields() as $field) {
+ $data[$field->getName()] = $this->request->input($field->getName());
+ }
+ return $this->saveData($data);
+ }
+}
diff --git a/src/Services/CrudBoard/CrudGrid.php b/src/Services/CrudBoard/CrudGrid.php
new file mode 100644
index 0000000..252685e
--- /dev/null
+++ b/src/Services/CrudBoard/CrudGrid.php
@@ -0,0 +1,196 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Services\CrudBoard;
+
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use CodeCoz\AimAdmin\Collection\FieldCollection;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudGridInterface;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudGridLoaderInterface;
+use Illuminate\Support\Facades\Request;
+use Illuminate\Support\Collection;
+use Illuminate\Pagination\LengthAwarePaginator;
+use CodeCoz\AimAdmin\Dto\CrudBoard\FieldDto;
+use CodeCoz\AimAdmin\Field\Field;
+
+/**
+ * This class implement AimAdmin CRUD grid Interface .
+ * It is responsible to generate grid/table view.
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+final class CrudGrid implements CrudGridInterface
+{
+ private ?string $title = null;
+ private ?bool $disableSerialColumn = false;
+ private FieldCollection $fields;
+ private ActionCollection $actions;
+ private GridFilter $filter;
+ private GridPaginator $paginator;
+ private ?LengthAwarePaginator $gridData = null;
+ private array $config = [
+ 'actionHeader' => 'Action',
+ 'rowCssCallable' => null,
+ 'rowCssClassCallable' => null,
+ 'headerRowCssClass' => null,
+ 'tableCssClass' => 'table-bordered table-hover text-nowrap table-sm',
+ ];
+
+ private function __construct(private CrudGridLoaderInterface $gridDataLoader, array $params)
+ {
+ $this->filter = new GridFilter();
+ $this->paginator = new GridPaginator($params['pagination'] ?? 10);
+ $this->config = ($params['config'] ?? []) + $this->config;
+ }
+
+ public static function init(CrudGridLoaderInterface $gridDataLoader, array $params): CrudGridInterface
+ {
+ return new self($gridDataLoader, $params);
+ }
+
+ public function setTitle(string $title): self
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ public function getTitle(): ?string
+ {
+ return $this->title;
+ }
+
+ public function getFilter(): GridFilter
+ {
+ return $this->filter;
+ }
+
+ function getGridDataLoader(): CrudGridLoaderInterface
+ {
+ return $this->gridDataLoader;
+ }
+
+ public function addColumns(array $columns): self
+ {
+ $this->fields = FieldCollection::init($columns);
+ return $this;
+ }
+
+ public function addActions(array $actions = [])
+ {
+ $dtos = [];
+ foreach ($actions as $name => $action) {
+ $dtos[$name] = $action->getDto();
+ }
+ $this->actions = ActionCollection::init($dtos);
+ return $this;
+ }
+
+ public function getColumns(): FieldCollection
+ {
+ return $this->fields;
+ }
+
+ public function getGridData(): LengthAwarePaginator
+ {
+ if (!$this->gridData) {
+ $filters = $this->filter->getData();
+ $queryBuilder = $this->gridDataLoader->getGridQuery();
+ if (null !== $queryBuilder) {
+ $queryBuilder = $this->gridDataLoader->applyFilterQuery($queryBuilder, $filters);
+ $paginator = $this->paginator->paginateQuery($queryBuilder);
+ } elseif ($gridData = $this->gridDataLoader->getGridData($filters)) {
+ $dataCollection = $gridData instanceof Collection ? $gridData : collect($gridData);
+ $dataCollection = $this->gridDataLoader->applyFilterData($dataCollection, $filters);
+ $paginator = $this->paginator->paginate($dataCollection);
+ $paginator->withPath(Request::url());
+ } else {
+ ($paginator = $this->gridDataLoader->getGridCursorPaginator($filters))
+ || ($paginator = $this->gridDataLoader->getGridPaginator($filters));
+ }
+ $this->gridData = $paginator ? $paginator->withQueryString() : $this->paginator->paginate(collect([]));
+ }
+ return $this->gridData;
+ }
+
+ public function getActions(): ActionCollection
+ {
+ return $this->actions->getCrudBoardActions();
+ }
+
+ public function getRowActions(): ActionCollection
+ {
+ return $this->actions->getRowActions();
+ }
+
+ public function getActionLevel(): string
+ {
+ return $this->config['actionHeader'];
+ }
+
+ public function getRowCssClass(mixed $row): ?string
+ {
+ $cssClass = null;
+ $this->config['rowCssClassCallable'] &&
+ is_callable($this->config['rowCssClassCallable']) &&
+ $cssClass = call_user_func($this->config['rowCssClassCallable'], $row);
+ return $cssClass;
+ }
+
+ public function getRowCss(mixed $row): ?string
+ {
+ $css = null;
+ $this->config['rowCssCallable'] &&
+ is_callable($this->config['rowCssCallable']) &&
+ $css = call_user_func($this->config['rowCssCallable'], $row);
+ return $css;
+ }
+
+ public function getHeaderRowCssClass(): ?string
+ {
+ return $this->config['headerRowCssClass'];
+ }
+
+ public function getTableCssClass(): ?string
+ {
+ return $this->config['tableCssClass'];
+ }
+
+ public function isDisableSerialColumn(): ?bool
+ {
+ return $this->disableSerialColumn;
+ }
+
+ public function disableSerialColumn(bool $disable = true): void
+ {
+ $this->disableSerialColumn = $disable;
+ }
+
+ public function enableIdCheckBox(FieldDto $fieldDto = null): void
+ {
+ $this->disableSerialColumn();
+
+ $field = Field::init('id')->setLabel("
+
+
+
+
+ ")->setComponent('aim-admin::field.id')
+ ->getDto();
+
+ // Use the provided FieldDto if given, otherwise use the one created above
+ $fieldDto = $fieldDto ?? $field;
+
+ $this->fields->prepend($fieldDto);
+ }
+
+}
diff --git a/src/Services/CrudBoard/CrudShow.php b/src/Services/CrudBoard/CrudShow.php
new file mode 100644
index 0000000..1bc6678
--- /dev/null
+++ b/src/Services/CrudBoard/CrudShow.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Services\CrudBoard;
+
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudShowInterface;
+use CodeCoz\AimAdmin\Collection\FieldCollection;
+
+class CrudShow implements CrudShowInterface
+{
+ private FieldCollection $fields;
+ private ActionCollection $actions;
+ private ?string $title = null;
+
+ private function __construct( array $fields, private \ArrayAccess $record)
+ {
+ $this->addFields($fields)
+ ->prepareRecord($record);
+
+ }
+
+ public static function init(array $fields, \ArrayAccess $record)
+ {
+ return new self($fields,$record);
+ }
+
+ public function addFields($fields)
+ {
+ $this->fields = FieldCollection::init($fields);
+ return $this;
+ }
+
+ public function addActions(array $actions=[])
+ {
+ $dtos = [];
+ foreach($actions as $name => $action){
+ $dtos[$name] = $action->getDto();
+ }
+ $this->actions = ActionCollection::init($dtos);
+ return $this;
+ }
+
+ public function getActions() : ActionCollection
+ {
+ return $this->actions;
+ }
+
+ public function getFields(): FieldCollection
+ {
+ return $this->fields;
+ }
+
+ private function prepareRecord($record)
+ {
+ foreach($this->fields as $name=>&$field)
+ {
+ $field->setValue($record[$name]);
+ }
+ }
+
+ public function getRecord() : \ArrayAccess
+ {
+ return $this->record;
+ }
+
+ public function setTitle(string $title) : self
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ public function getTitle() : ?string
+ {
+ return $this->title;
+ }
+}
diff --git a/src/Services/CrudBoard/GridFilter.php b/src/Services/CrudBoard/GridFilter.php
new file mode 100644
index 0000000..3296c4c
--- /dev/null
+++ b/src/Services/CrudBoard/GridFilter.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace CodeCoz\AimAdmin\Services\CrudBoard;
+
+use CodeCoz\AimAdmin\Form\AbstractForm;
+use CodeCoz\AimAdmin\Collection\ActionCollection;
+use Illuminate\Support\Facades\Request;
+use CodeCoz\AimAdmin\Field\ButtonField;
+
+ /**
+ * This is an abstract form class for crud board
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+
+ class GridFilter extends AbstractForm
+ {
+ const CONTAINER_NAME = 'filters';
+
+ public function getActions() : ActionCollection
+ {
+ return $this->actions;
+ }
+
+ public function assignQueryData() : self
+ {
+ $queryData = Request::get(self::CONTAINER_NAME,[]);
+ // dd($queryData);
+ $fields = $this->getFields();
+ foreach($queryData as $field=>$value){
+ if($fields->offsetExists($field)) {
+ $value && $fields->get($field)->setValue($value);
+ }
+ }
+ return $this;
+ }
+
+ public function getData(): array
+ {
+ $data = Request::get(self::CONTAINER_NAME,[]);
+ return array_filter($data);
+ }
+
+ public function setActions(array $actions) : static
+ {
+ $dtos = [];
+ foreach($actions as $action) {
+ $dto = $action->getDto();
+ $dto->isFilterAction() && $dtos[] = $dto;
+ }
+ if(empty($dtos)){
+ $actions = [
+ ButtonField::init('Search','Search')->createAsFilterSubmitAction()
+ ->setIcon('fa-search'),
+ ButtonField::init('reset','Reset')->createAsFilterAction()
+ ->displayAsButton()->setHtmlAttributes(['type'=>'reset']),
+ ];
+ foreach($actions as $action) {
+ $dto = $action->getDto();
+ $dto->isFilterAction() && $dtos[] = $dto;
+ }
+ }
+ $this->actions = ActionCollection::init($dtos);
+ return $this;
+ }
+
+
+ }
diff --git a/src/Services/CrudBoard/GridPaginator.php b/src/Services/CrudBoard/GridPaginator.php
new file mode 100644
index 0000000..1067bed
--- /dev/null
+++ b/src/Services/CrudBoard/GridPaginator.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Services\CrudBoard;
+
+use Illuminate\Pagination\Paginator;
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Collection;
+
+/**
+ * This is an abstract form class for crud board
+ *
+ * @author Muhammad Abdullah Ibne Masud
+ */
+class GridPaginator
+{
+
+ public function __construct(private readonly int $recordPerPage = 3)
+ {
+ }
+
+ public function paginate(Collection $data, $options = []): LengthAwarePaginator
+ {
+ $page = Paginator::resolveCurrentPage();
+ return new LengthAwarePaginator($data->forPage($page, $this->recordPerPage), $data->count(), $this->recordPerPage, $page, $options);
+ }
+
+ public function paginateQuery(Builder $queryBuilder): LengthAwarePaginator
+ {
+ return $queryBuilder->paginate($this->recordPerPage);
+ }
+
+}
diff --git a/src/Support/Facades/CrudBoardFacade.php b/src/Support/Facades/CrudBoardFacade.php
new file mode 100644
index 0000000..43d4d17
--- /dev/null
+++ b/src/Support/Facades/CrudBoardFacade.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Support\Facades;
+
+use Illuminate\Support\Facades\Facade;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudBoardInterface;
+
+/**
+ * This is a facade class for crud grid
+ *
+ * @author CodeCoz
+ */
+class CrudBoardFacade extends Facade
+{
+ /**
+ * Get the registered name of the component.
+ *
+ * @return string
+ */
+ protected static function getFacadeAccessor(): string
+ {
+ return CrudBoardInterface::class;
+ }
+}
diff --git a/src/Support/OptionValueStore.php b/src/Support/OptionValueStore.php
new file mode 100644
index 0000000..f6e97c7
--- /dev/null
+++ b/src/Support/OptionValueStore.php
@@ -0,0 +1,178 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\Support;
+
+use Countable;
+
+final class OptionValueStore implements Countable
+{
+ private array $container = [];
+
+ protected string $delimiter = '.';
+
+ private function __construct(array $options, $delimiter)
+ {
+ $this->container = $options;
+ $this->delimiter = $delimiter;
+ }
+
+ public static function init(array $options = [], $delimiter = '.'): self
+ {
+ return new self($options, $delimiter);
+ }
+
+ public function all(): array
+ {
+ return $this->container;
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ if (is_null($offset)) {
+ $this->container[] = $value;
+ } else {
+ $this->container[$offset] = $value;
+ }
+ }
+
+ protected function exists($array, $key): bool
+ {
+ return \array_key_exists($key, $array);
+ }
+
+ public function count(): int
+ {
+ return \count($this->all());
+ }
+
+ public function isEmpty(): bool
+ {
+ return 0 === $this->count();
+ }
+
+ public function has($keys): bool
+ {
+ $keys = (array)$keys;
+
+ if (!$this->container || $keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $items = $this->container;
+
+ if ($this->exists($items, $key)) {
+ continue;
+ }
+
+ foreach (explode($this->delimiter, $key) as $segment) {
+ if (!is_array($items) || !$this->exists($items, $segment)) {
+ return false;
+ }
+
+ $items = $items[$segment];
+ }
+ }
+
+ return true;
+ }
+
+ public function get($key = null, $default = null)
+ {
+ if (\is_null($key)) {
+ return $this->container;
+ }
+
+ if ($this->exists($this->container, $key)) {
+ return $this->container[$key];
+ }
+
+ if (!\str_contains($key, $this->delimiter)) {
+ return $default;
+ }
+
+ $items = $this->container;
+
+ foreach (\explode($this->delimiter, $key) as $segment) {
+ if (!\is_array($items) || !$this->exists($items, $segment)) {
+ return $default;
+ }
+
+ $items = &$items[$segment];
+ }
+
+ return $items;
+ }
+
+ public function set($keys, $value = null): void
+ {
+ if (\is_array($keys)) {
+ foreach ($keys as $key => $value) {
+ $this->set($key, $value);
+ }
+
+ return;
+ }
+
+ $items = &$this->container;
+
+ foreach (\explode($this->delimiter, $keys) as $key) {
+ if (!isset($items[$key]) || !\is_array($items[$key])) {
+ $items[$key] = [];
+ }
+
+ $items = &$items[$key];
+ }
+
+ $items = $value;
+ }
+
+ public function setIfNotSet(string $key, mixed $value): void
+ {
+ if (!$this->has($key)) {
+ $this->set($key, $value);
+ }
+ }
+
+ public function add($keys, $value = null)
+ {
+ if (is_array($keys)) {
+ foreach ($keys as $key => $value) {
+ $this->add($key, $value);
+ }
+ } elseif ($this->get($keys) === null) {
+ $this->set($keys, $value);
+ }
+
+ return $this;
+ }
+
+ public function keyValueExists($key, $value): bool
+ {
+ $keyValues = $this->get($key);
+ if ($keyValues) {
+ return is_array($keyValues) ? \in_array($value, $keyValues) : ($keyValues == $value);
+ }
+ return false;
+ }
+
+ public function renderAsHtmlAttributes(): string
+ {
+ $html = "";
+ foreach ($this->all() as $key => $value) {
+ $html .= "$key='$value'";
+ }
+ return $html;
+ }
+
+
+}
diff --git a/src/View/Components/AbstractAimAdminComponent.php b/src/View/Components/AbstractAimAdminComponent.php
new file mode 100644
index 0000000..3559fa4
--- /dev/null
+++ b/src/View/Components/AbstractAimAdminComponent.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\View\Components;
+
+use Illuminate\View\Component;
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudBoardInterface;
+
+
+/**
+ * this is a base component class of AimAdmin platform.
+ *
+ * @author CodeCoz
+ */
+abstract class AbstractAimAdminComponent extends Component
+{
+
+ public function __construct(private CrudBoardInterface $crudBoard)
+ {
+ }
+
+ public function getCrudBoard(): CrudBoardInterface
+ {
+ return $this->crudBoard;
+ }
+
+}
diff --git a/src/View/Components/CrudForm.php b/src/View/Components/CrudForm.php
new file mode 100644
index 0000000..5281db9
--- /dev/null
+++ b/src/View/Components/CrudForm.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\View\Components;
+
+use CodeCoz\AimAdmin\Form\CrudForm as Form;
+
+/**
+ * this is a component class responsible for Crud Grid view
+ *
+ * @author CodeCoz
+ */
+class CrudForm extends AbstractAimAdminComponent
+{
+ public Form $form;
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\View\View|\Closure|string
+ */
+ public function render()
+ {
+ $this->form = $this->getCrudBoard()->getForm();
+ return view('aim-admin::crudboard.form');
+ }
+}
diff --git a/src/View/Components/CrudGrid.php b/src/View/Components/CrudGrid.php
new file mode 100644
index 0000000..d0c8d94
--- /dev/null
+++ b/src/View/Components/CrudGrid.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\View\Components;
+
+use CodeCoz\AimAdmin\Contracts\Service\CrudBoard\CrudGridInterface;
+
+/**
+ * this is a component class responsible for Crud Grid view
+ *
+ * @author CodeCoz
+ */
+class CrudGrid extends AbstractAimAdminComponent
+{
+
+ public CrudGridInterface $grid;
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\View\View|\Closure|string
+ */
+ public function render()
+ {
+ $this->grid = $this->getCrudBoard()->getGrid();
+ return view('aim-admin::crudboard.grid');
+ }
+}
diff --git a/src/View/Components/CrudShow.php b/src/View/Components/CrudShow.php
new file mode 100644
index 0000000..71a812a
--- /dev/null
+++ b/src/View/Components/CrudShow.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\View\Components;
+
+use CodeCoz\AimAdmin\Services\CrudBoard\CrudShow as Show;
+
+/**
+ * this is a component class responsible for Crud Grid view
+ *
+ * @author CodeCoz
+ */
+class CrudShow extends AbstractAimAdminComponent
+{
+
+ public Show $show;
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\View\View|\Closure|string
+ */
+ public function render()
+ {
+ $this->show = $this->getCrudBoard()->getCrudShow();
+ return view('aim-admin::crudboard.show');
+ }
+}
diff --git a/src/View/Components/GridFilter.php b/src/View/Components/GridFilter.php
new file mode 100644
index 0000000..ab9557b
--- /dev/null
+++ b/src/View/Components/GridFilter.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\View\Components;
+
+use CodeCoz\AimAdmin\Services\CrudBoard\GridFilter as Filter;
+
+/**
+ * this is a component class responsible for Crud Grid view
+ *
+ * @author CodeCoz
+ */
+class GridFilter extends AbstractAimAdminComponent
+{
+
+ public Filter $filter;
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\View\View|\Closure|string
+ */
+ public function render()
+ {
+ $this->prepareFields();
+ return view('aim-admin::crudboard.filter');
+ }
+
+ private function prepareFields(): void
+ {
+ $filter = $this->getCrudBoard()
+ ->getGrid()->getFilter();
+ $fields = $filter->getFields();
+ foreach ($fields as $name => $field) {
+ $field->setName(Filter::CONTAINER_NAME . "[$name]");
+ }
+ $this->filter = $filter;
+ }
+}
diff --git a/src/View/Components/MainLayout.php b/src/View/Components/MainLayout.php
new file mode 100644
index 0000000..0d58999
--- /dev/null
+++ b/src/View/Components/MainLayout.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace CodeCoz\AimAdmin\View\Components;
+
+use Illuminate\View\Component;
+use Illuminate\View\View;
+
+/**
+ * this is a component class responsible for Crud Grid view
+ *
+ * @author CodeCoz
+ */
+class Toast extends Component
+{
+ /**
+ * Create the component instance.
+ */
+ public function __construct(
+ public string $type,
+ public string $message,
+ public int $timer = 2000,
+ )
+ {
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ */
+ public function render(): View
+ {
+ return view('aim-admin::crudboard.toast');
+ }
+
+}
diff --git a/src/stubs/app/Models/User.stub b/src/stubs/app/Models/User.stub
new file mode 100644
index 0000000..485248e
--- /dev/null
+++ b/src/stubs/app/Models/User.stub
@@ -0,0 +1,11 @@
+
+
+ Aim :: Admin Dashboard
+
+
+
+
Admin Dashboard
+
+
+
+
diff --git a/src/stubs/route/web.php.stub b/src/stubs/route/web.php.stub
new file mode 100644
index 0000000..4e77542
--- /dev/null
+++ b/src/stubs/route/web.php.stub
@@ -0,0 +1,5 @@
+Route::group(['middleware' => ['web', 'auth']], function () {
+ Route::get('/dashboard', function () {
+ return view('dashboard');
+ })->name('dashboard');
+});
diff --git a/src/stubs/vite.config.js b/src/stubs/vite.config.js
new file mode 100644
index 0000000..3600312
--- /dev/null
+++ b/src/stubs/vite.config.js
@@ -0,0 +1,46 @@
+import {defineConfig} from 'vite';
+import laravel from 'laravel-vite-plugin';
+import {viteStaticCopy} from 'vite-plugin-static-copy'
+
+export default defineConfig({
+ plugins: [
+ laravel({
+ input: [
+ 'resources/js/app.js'
+ ],
+ refresh: true,
+ }),
+ viteStaticCopy({
+ targets: [
+ {
+ src: 'vendor/codecoz/aim-admin/resources/img',
+ dest: '../'
+ },
+ {
+ src: 'node_modules/admin-lte/dist/img',
+ dest: '../dist'
+ }
+ ]
+ })
+ ],
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (id.includes('node_modules')) {
+ return 'vendor'; // split vendor modules into a separate chunk
+ }
+ // more conditions for other chunks
+ }
+ }
+ },
+ chunkSizeWarningLimit: 500, // Adjust the chunk size limit
+ },
+ server: {
+ hmr: {
+ host: 'localhost',
+ protocol: 'ws',
+ port: 3000
+ }
+ },
+});
diff --git a/tests/ComponentTestCase.php b/tests/ComponentTestCase.php
new file mode 100644
index 0000000..d73ae68
--- /dev/null
+++ b/tests/ComponentTestCase.php
@@ -0,0 +1,55 @@
+artisan('view:clear');
+ }
+
+ protected function flashOld(array $input): void
+ {
+ session()->flashInput($input);
+ $sessionStore = session()->driver();
+ request()->setLaravelSession($sessionStore);
+ }
+
+
+ protected function getPackageProviders($app): array
+ {
+ return [AdminServiceProvider::class];
+ }
+
+ /**
+ * @throws RuntimeException
+ * @throws InvalidArgumentException|\Throwable
+ */
+ public function assertComponentRenders(string $expected, string $template, array $data = []): void
+ {
+ $indenter = new Indenter();
+ $indenter->setElementType('h1', Indenter::ELEMENT_TYPE_INLINE);
+ $indenter->setElementType('del', Indenter::ELEMENT_TYPE_INLINE);
+
+ $blade = (string)$this->blade($template, $data);
+ $indented = $indenter->indent($blade);
+ $cleaned = str_replace(
+ [' >', "\n/>", ' ', '> ', "\n>"],
+ ['>', ' />', "\n", ">\n ", '>'],
+ $indented,
+ );
+
+ $this->assertSame($expected, $cleaned);
+ }
+}
diff --git a/tests/Components/ButtonFieldTest.php b/tests/Components/ButtonFieldTest.php
new file mode 100644
index 0000000..67ea899
--- /dev/null
+++ b/tests/Components/ButtonFieldTest.php
@@ -0,0 +1,90 @@
+assertInstanceOf(ButtonField::class, $component);
+ }
+
+ #[Test] public function it_sets_the_html_element_as_anchor()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('a', $component->getDto()->getHtmlElement());
+ }
+
+ #[Test] public function it_sets_the_button_label_correctly()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('Create', $component->getDto()->getLabel());
+ }
+
+
+ #[Test] public function it_sets_the_title_attribute_correctly()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('Create', $component->getDto()->getHtmlAttributes()->get('title'));
+ }
+
+
+ #[Test] public function it_sets_the_button_value_correctly()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('name', $component->getDto()->getValue());
+ }
+
+ #[Test] public function it_sets_the_css_class_correctly()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('btn btn-block btn-sm btn-primary', $component->getDto()->getCssClass());
+ }
+
+
+ #[Test] public function it_sets_the_button_type_correctly()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('row', $component->getDto()->getType());
+ }
+
+ #[Test] public function it_adds_custom_html_attributes()
+ {
+ $component = ButtonField::init('name', 'Create')
+ ->setHtmlAttributes(['data-custom' => 'custom-value']);
+ $this->assertEquals('custom-value', $component->getDto()->getHtmlAttributes()->get('data-custom'));
+ }
+
+
+ #[Test] public function it_can_render_with_custom_url()
+ {
+ $component = ButtonField::init('name', 'Create')
+ ->createAsFormAction()
+ ->linkToUrl('/custom-url');
+ $this->assertEquals('/custom-url', $component->getDto()->getUrl());
+ }
+
+
+ #[Test] public function it_can_render_with_custom_route()
+ {
+ $component = ButtonField::init('name', 'Create')
+ ->createAsFormAction()
+ ->linkToRoute('custom.route', ['param' => 'value']);
+ $this->assertEquals('custom.route', $component->getDto()->getRouteName());
+ $this->assertEquals(['param' => 'value'], $component->getDto()->getRouteParameters());
+ }
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $component = ButtonField::init('name', 'Create');
+ $this->assertEquals('aim-admin::crudboard.actions.grid-button', $component->getDto()->getComponent());
+ }
+
+}
diff --git a/tests/Components/CheckBoxFieldTest.php b/tests/Components/CheckBoxFieldTest.php
new file mode 100644
index 0000000..5181407
--- /dev/null
+++ b/tests/Components/CheckBoxFieldTest.php
@@ -0,0 +1,57 @@
+component = CheckBoxField::init('name', 'CheckBox');
+ }
+
+ #[Test] public function the_checkbox_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(CheckBoxField::class, $this->component);
+ }
+
+ #[Test] public function it_sets_the_html_element_as_anchor()
+ {
+ $this->assertEquals('input', $this->component->getDto()->getHtmlElement());
+ }
+
+ #[Test] public function it_sets_the_checkbox_label_correctly()
+ {
+ $this->assertEquals('CheckBox', $this->component->getDto()->getLabel());
+ }
+
+ #[Test] public function it_sets_the_checkbox_value_correctly()
+ {
+ $this->assertEquals(null, $this->component->getDto()->getValue());
+ }
+
+ #[Test] public function it_sets_the_checkbox_type_correctly()
+ {
+ $this->assertEquals('checkbox', $this->component->getDto()->getInputType());
+ }
+
+ #[Test] public function it_adds_custom_html_attributes()
+ {
+ $component = CheckBoxField::init('name', 'CheckBox')
+ ->setHtmlAttributes(['data-custom' => 'custom-value']);
+ $this->assertEquals('custom-value', $component->getDto()->getHtmlAttributes()->get('data-custom'));
+ }
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.checkbox', $this->component->getDto()->getComponent());
+ }
+
+}
diff --git a/tests/Components/DateTimeFieldTest.php b/tests/Components/DateTimeFieldTest.php
new file mode 100644
index 0000000..db081cb
--- /dev/null
+++ b/tests/Components/DateTimeFieldTest.php
@@ -0,0 +1,34 @@
+component = DateTimeField::init('date', 'Date');
+ }
+
+ #[Test]
+ public function it_initializes_with_correct_attributes()
+ {
+
+ $this->assertEquals('date', $this->component->getDto()->getName());
+ $this->assertEquals('Date', $this->component->getDto()->getLabel());
+ }
+
+ #[Test]
+ public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.datetime', $this->component->getDto()->getComponent());
+ }
+}
diff --git a/tests/Components/DropDownFieldTest.php b/tests/Components/DropDownFieldTest.php
new file mode 100644
index 0000000..f9a02fe
--- /dev/null
+++ b/tests/Components/DropDownFieldTest.php
@@ -0,0 +1,81 @@
+options = [
+ 1 => 'One',
+ 2 => 'Two',
+ ];
+ $this->component = ChoiceField::init('type', 'Type', choiceType: 'select', choiceList: $this->options);
+ }
+
+ #[Test] public function the_choice_field_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(ChoiceField::class, $this->component);
+ }
+
+ #[Test] public function it_sets_the_required_html_attribute_correctly()
+ {
+ $component = $this->component->setHtmlAttributes(['required' => true]);
+ $this->assertTrue($component->getDto()->getHtmlAttributes()->get('required'));
+ }
+
+ #[Test] public function it_sets_the_choice_list_correctly()
+ {
+ $this->assertEquals($this->options, $this->component->getDto()->getCustomOption('choiceList'));
+ }
+
+ #[Test] public function it_sets_the_input_type_correctly()
+ {
+ $this->assertEquals('select', $this->component->getDto()->getInputType());
+ }
+
+ #[Test] public function it_sets_the_default_value_correctly()
+ {
+ $component = $this->component->setDefaultValue('1');
+ $this->assertEquals('1', $component->getDto()->getValue());
+ }
+
+ #[Test] public function it_handles_custom_options_correctly()
+ {
+ $component = $this->component->setCustomOption('empty', 'Select an option');
+ $this->assertEquals('Select an option', $component->getDto()->getCustomOption('empty'));
+ }
+
+ #[Test] public function it_sets_the_name_and_label_correctly()
+ {
+ $this->assertEquals('type', $this->component->getDto()->getName());
+ $this->assertEquals('Type', $this->component->getDto()->getLabel());
+ }
+
+ #[Test] public function it_defaults_css_class_to_empty_string()
+ {
+ $this->assertEquals('', $this->component->getDto()->getCssClass());
+ }
+
+ #[Test] public function it_sets_the_layout_class_correctly()
+ {
+ $this->assertEquals('col-lg-6', $this->component->getDto()->getLayoutClass());
+ }
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.choice', $this->component->getDto()->getComponent());
+ }
+
+
+}
diff --git a/tests/Components/FieldGroupTest.php b/tests/Components/FieldGroupTest.php
new file mode 100644
index 0000000..fe1baa7
--- /dev/null
+++ b/tests/Components/FieldGroupTest.php
@@ -0,0 +1,54 @@
+component = FieldGroup::init([
+ TextField::init('name', 'Product Name'),
+ TextField::init('detail', 'Product Detail'),
+ TextField::init('source', 'Product Source'),
+ ], 'Group')
+ ->setLayoutClass('col-12');
+ }
+
+ #[Test] public function the_field_group_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(FieldGroup::class, $this->component);
+ }
+
+ #[Test] public function it_sets_the_field_group_label_correctly()
+ {
+ $this->assertEquals('Group', $this->component->getDto()->getLabel());
+ }
+
+ #[Test] public function it_sets_the_field_group_value_correctly()
+ {
+ $this->assertEquals(null, $this->component->getDto()->getValue());
+ }
+
+ #[Test] public function it_adds_custom_html_attributes()
+ {
+ $component = $this->component
+ ->setHtmlAttributes(['data-custom' => 'custom-value']);
+ $this->assertEquals('custom-value', $component->getDto()->getHtmlAttributes()->get('data-custom'));
+ }
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.group', $this->component->getDto()->getComponent());
+ }
+
+}
diff --git a/tests/Components/FileFieldTest.php b/tests/Components/FileFieldTest.php
new file mode 100644
index 0000000..2640c7a
--- /dev/null
+++ b/tests/Components/FileFieldTest.php
@@ -0,0 +1,94 @@
+component = FileField::init('name', 'file-name');
+ }
+
+ #[Test] public function it_initializes_with_correct_attributes()
+ {
+ $this->assertEquals('name', $this->component->getDto()->getName());
+ $this->assertEquals('File-name', $this->component->getDto()->getLabel());
+ $this->assertEquals('file', $this->component->getDto()->getInputType());
+ $this->assertEquals('input', $this->component->getDto()->getHtmlElement());
+ $this->assertEquals(null, $this->component->getDto()->getHtmlAttributes()->get('id'));
+ }
+
+ #[Test] public function the_text_filed_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(FileField::class, $this->component);
+ }
+
+ #[Test] public function it_sets_the_html_element_as_input()
+ {
+ $this->assertEquals('input', $this->component->getDto()->getHtmlElement());
+ }
+
+ #[Test] public function it_sets_the_placeholder_attribute()
+ {
+ $this->assertEquals('file-name', $this->component->getDto()->getHtmlAttributes()->get('placeholder'));
+ }
+
+ #[Test] public function it_sets_the_id_attribute()
+ {
+ $this->assertEquals(null, $this->component->getDto()->getHtmlAttributes()->get('id'));
+ }
+
+ #[Test] public function it_assigns_the_label_correctly()
+ {
+ $this->assertEquals('File-name', $this->component->getDto()->getLabel());
+ }
+
+ #[Test] public function it_defaults_to_not_required()
+ {
+ $this->assertFalse($this->component->getDto()->isRequired());
+ }
+
+
+ #[Test] public function it_handles_custom_options()
+ {
+ $component = $this->component->setCustomOption('data-custom', 'value');
+ $this->assertEquals('value', $component->getDto()->getCustomOption('data-custom'));
+ }
+
+
+ #[Test] public function it_defaults_css_class_to_empty_string()
+ {
+ $this->assertEquals('', $this->component->getDto()->getCssClass());
+ }
+
+
+ #[Test] public function it_stores_html_attributes_correctly()
+ {
+ $component = $this->component->setHtmlAttributes(['data-test' => 'test-value']);
+ $this->assertEquals('test-value', $component->getDto()->getHtmlAttributes()->get('data-test'));
+ }
+
+
+ #[Test] public function it_sets_the_layout_class_correctly()
+ {
+ $this->assertEquals('col-lg-6', $this->component->getDto()->getLayoutClass());
+ }
+
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.file', $this->component->getDto()->getComponent());
+ }
+
+}
diff --git a/tests/Components/HiddenFieldTest.php b/tests/Components/HiddenFieldTest.php
new file mode 100644
index 0000000..afc2d24
--- /dev/null
+++ b/tests/Components/HiddenFieldTest.php
@@ -0,0 +1,86 @@
+component = HiddenField::init('name', 'input-name');
+ }
+
+ #[Test] public function it_initializes_with_correct_attributes()
+ {
+ $this->assertEquals('name', $this->component->getDto()->getName());
+ $this->assertEquals(null, $this->component->getDto()->getLabel());
+ }
+
+ #[Test] public function the_text_filed_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(HiddenField::class, $this->component);
+ }
+
+
+ #[Test] public function it_sets_the_placeholder_attribute()
+ {
+ $component = $this->component->setPlaceholder('Placeholder');
+ $this->assertEquals('Placeholder', $component->getDto()->getHtmlAttributes()->get('placeholder'));
+ }
+
+
+ #[Test] public function it_assigns_the_label_correctly()
+ {
+ $this->assertEquals(null, $this->component->getDto()->getLabel());
+ }
+
+
+ #[Test] public function it_defaults_to_not_required()
+ {
+ $this->assertFalse($this->component->getDto()->isRequired());
+ }
+
+
+ #[Test] public function it_handles_custom_options()
+ {
+ $component = $this->component->setCustomOption('data-custom', 'value');
+ $this->assertEquals('value', $component->getDto()->getCustomOption('data-custom'));
+ }
+
+
+ #[Test] public function it_defaults_css_class_to_empty_string()
+ {
+ $this->assertEquals('', $this->component->getDto()->getCssClass());
+ }
+
+
+ #[Test] public function it_stores_html_attributes_correctly()
+ {
+ $component = $this->component->setHtmlAttributes(['data-test' => 'test-value']);
+ $this->assertEquals('test-value', $component->getDto()->getHtmlAttributes()->get('data-test'));
+ }
+
+
+ #[Test] public function it_sets_the_layout_class_correctly()
+ {
+ $this->assertEquals('col-lg-6', $this->component->getDto()->getLayoutClass());
+ }
+
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.hidden', $this->component->getDto()->getComponent());
+ }
+
+
+}
diff --git a/tests/Components/TextAreaFieldTest.php b/tests/Components/TextAreaFieldTest.php
new file mode 100644
index 0000000..76258cd
--- /dev/null
+++ b/tests/Components/TextAreaFieldTest.php
@@ -0,0 +1,91 @@
+component = TextareaField::init('name', 'textarea-name');
+ }
+
+ #[Test] public function it_initializes_with_correct_attributes()
+ {
+ $this->assertEquals('name', $this->component->getDto()->getName());
+ $this->assertEquals('Textarea-name', $this->component->getDto()->getLabel());
+ $this->assertEquals('name', $this->component->getDto()->getHtmlAttributes()->get('id'));
+ }
+
+ #[Test] public function the_text_filed_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(TextareaField::class, $this->component);
+ }
+
+
+ #[Test] public function it_sets_the_placeholder_attribute()
+ {
+ $component = $this->component->setPlaceholder('Placeholder');
+ $this->assertEquals('Placeholder', $component->getDto()->getHtmlAttributes()->get('placeholder'));
+ }
+
+ #[Test] public function it_sets_the_id_attribute()
+ {
+ $this->assertEquals('name', $this->component->getDto()->getHtmlAttributes()->get('id'));
+ }
+
+ #[Test] public function it_assigns_the_label_correctly()
+ {
+ $this->assertEquals('Textarea-name', $this->component->getDto()->getLabel());
+ }
+
+
+ #[Test] public function it_defaults_to_not_required()
+ {
+ $this->assertFalse($this->component->getDto()->isRequired());
+ }
+
+
+ #[Test] public function it_handles_custom_options()
+ {
+ $component = $this->component->setCustomOption('data-custom', 'value');
+ $this->assertEquals('value', $component->getDto()->getCustomOption('data-custom'));
+ }
+
+
+ #[Test] public function it_defaults_css_class_to_empty_string()
+ {
+ $this->assertEquals('', $this->component->getDto()->getCssClass());
+ }
+
+
+ #[Test] public function it_stores_html_attributes_correctly()
+ {
+ $component = $this->component->setHtmlAttributes(['data-test' => 'test-value']);
+ $this->assertEquals('test-value', $component->getDto()->getHtmlAttributes()->get('data-test'));
+ }
+
+
+ #[Test] public function it_sets_the_layout_class_correctly()
+ {
+ $this->assertEquals('col-lg-6', $this->component->getDto()->getLayoutClass());
+ }
+
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.textarea', $this->component->getDto()->getComponent());
+ }
+
+
+}
diff --git a/tests/Components/TextFieldTest.php b/tests/Components/TextFieldTest.php
new file mode 100644
index 0000000..8c4f568
--- /dev/null
+++ b/tests/Components/TextFieldTest.php
@@ -0,0 +1,102 @@
+component = TextField::init('name', 'input-name');
+ }
+
+ #[Test] public function it_initializes_with_correct_attributes()
+ {
+ $this->assertEquals('name', $this->component->getDto()->getName());
+ $this->assertEquals('Input-name', $this->component->getDto()->getLabel());
+ $this->assertEquals('text', $this->component->getDto()->getInputType());
+ $this->assertEquals('input', $this->component->getDto()->getHtmlElement());
+ $this->assertEquals('name', $this->component->getDto()->getHtmlAttributes()->get('id'));
+ }
+
+ #[Test] public function the_text_filed_component_can_be_rendered()
+ {
+ $this->assertInstanceOf(TextField::class, $this->component);
+ }
+
+
+ #[Test] public function it_sets_the_html_element_as_input()
+ {
+ $this->assertEquals('input', $this->component->getDto()->getHtmlElement());
+ }
+
+
+ #[Test] public function it_sets_the_placeholder_attribute()
+ {
+ $component = $this->component->setPlaceholder('Placeholder');
+ $this->assertEquals('Placeholder', $component->getDto()->getHtmlAttributes()->get('placeholder'));
+ }
+
+
+ #[Test] public function it_sets_the_id_attribute()
+ {
+ $this->assertEquals('name', $this->component->getDto()->getHtmlAttributes()->get('id'));
+ }
+
+
+ #[Test] public function it_assigns_the_label_correctly()
+ {
+ $this->assertEquals('Input-name', $this->component->getDto()->getLabel());
+ }
+
+
+ #[Test] public function it_defaults_to_not_required()
+ {
+ $this->assertFalse($this->component->getDto()->isRequired());
+ }
+
+
+ #[Test] public function it_handles_custom_options()
+ {
+ $component = $this->component->setCustomOption('data-custom', 'value');
+ $this->assertEquals('value', $component->getDto()->getCustomOption('data-custom'));
+ }
+
+
+ #[Test] public function it_defaults_css_class_to_empty_string()
+ {
+ $this->assertEquals('', $this->component->getDto()->getCssClass());
+ }
+
+
+ #[Test] public function it_stores_html_attributes_correctly()
+ {
+ $component = $this->component->setHtmlAttributes(['data-test' => 'test-value']);
+ $this->assertEquals('test-value', $component->getDto()->getHtmlAttributes()->get('data-test'));
+ }
+
+
+ #[Test] public function it_sets_the_layout_class_correctly()
+ {
+ $this->assertEquals('col-lg-6', $this->component->getDto()->getLayoutClass());
+ }
+
+
+ #[Test] public function it_sets_the_component_template_correctly()
+ {
+ $this->assertEquals('aim-admin::crudboard.fields.input', $this->component->getDto()->getComponent());
+ }
+
+
+}
diff --git a/tests/Service/CrudBoardTest.php b/tests/Service/CrudBoardTest.php
new file mode 100644
index 0000000..1d8661f
--- /dev/null
+++ b/tests/Service/CrudBoardTest.php
@@ -0,0 +1,97 @@
+crudBoard = new CrudBoard();
+ }
+
+ public function testCanCreateCrudForm()
+ {
+ $fields = ['name'];
+ $form = $this->crudBoard->createForm($fields);
+ $this->assertInstanceOf(CrudForm::class,$form);
+ $this->assertInstanceOf(CrudForm::class,$this->crudBoard->getForm());
+ }
+
+ public function testCrudBoardParameter()
+ {
+ $this->crudBoard->setParam('test1');
+ $this->crudBoard->setParam('test2');
+ $this->crudBoard->setParam('test3');
+ $this->assertSame(['test1','test2','test3'], $this->crudBoard->getParams());
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function testCrudBoardService()
+ {
+ $stubRepo = new class() implements AimAdminRepositoryInterface
+ {
+ public function getModelFqcn(): string
+ {
+ return "User";
+ }
+ public function crudShow(int|string $id): ?ArrayAccess
+ {
+ return $this->findById($id);
+ }
+ private function findById(int $id) : ArrayAccess{
+ $row = ['id'=>$id, 'title'=>'foo'];
+
+ return $stubRow = new class($row) implements ArrayAccess {
+ private array $container = [];
+ public function __construct(array $row)
+ {
+ $this->container = $row;
+ }
+ public function offsetSet($offset, $value): void {
+ if (is_null($offset)) {
+ $this->container[] = $value;
+ } else {
+ $this->container[$offset] = $value;
+ }
+ }
+
+ public function offsetExists($offset): bool {
+ return isset($this->container[$offset]);
+ }
+
+ public function offsetUnset($offset): void {
+ unset($this->container[$offset]);
+ }
+
+ public function offsetGet($offset): mixed {
+ return $this->container[$offset] ?? null;
+ }
+ };
+ }
+ };
+
+ $this->assertInstanceOf(CrudBoard::class,$this->crudBoard->setRepository($stubRepo));
+ $this->assertInstanceOf(AimAdminRepositoryInterface::class,$this->crudBoard->getRepository());
+ $crudShow = $this->crudBoard->createShow(1,['name']);
+ $this->assertInstanceOf(CrudShowInterface::class,$crudShow);
+
+ }
+
+
+}
+
+
diff --git a/tests/Service/CrudFormTest.php b/tests/Service/CrudFormTest.php
new file mode 100644
index 0000000..64d26b3
--- /dev/null
+++ b/tests/Service/CrudFormTest.php
@@ -0,0 +1,38 @@
+form = new CrudForm();
+ }
+
+ public function testFormField()
+ {
+ $this->form->addFields(['foo','bar']);
+ $this->assertInstanceOf(FormFieldCollection::class,$this->form->getFields());
+ }
+
+ public function testSetCssClass()
+ {
+ $this->form->setCssClass('foo')
+ ->setCssClass('bar');
+ $this->assertEquals('foo bar',trim($this->form->getCssClass()));
+ }
+
+ public function testAddIdField()
+ {
+ $this->form->addFields(['foo','bar']);
+ $this->form->addIdField();
+ $this->assertEquals(3,$this->form->getFields()->count());
+ }
+}
diff --git a/tests/Service/CrudGridTest.php b/tests/Service/CrudGridTest.php
new file mode 100644
index 0000000..82f614e
--- /dev/null
+++ b/tests/Service/CrudGridTest.php
@@ -0,0 +1,44 @@
+createMock(CrudGridLoaderInterface::class);
+ $this->crudGrid = CrudGrid::init($crudGridLoader,[]);
+ }
+
+ public function testTitle()
+ {
+ $this->crudGrid->setTitle('foo');
+ $this->assertEquals('foo',$this->crudGrid->getTitle(),'Should return foo');
+ }
+
+ public function testAddColumn()
+ {
+ $this->crudGrid->addColumns(['foo','bar']);
+ $this->assertInstanceOf(FieldCollection::class,$this->crudGrid->getColumns());
+ }
+
+ public function testDataLoader()
+ {
+ $this->assertInstanceOf(CrudGridLoaderInterface::class,$this->crudGrid->getGridDataLoader());
+ }
+
+
+}
diff --git a/tests/Service/GridFilterTest.php b/tests/Service/GridFilterTest.php
new file mode 100644
index 0000000..82f5fad
--- /dev/null
+++ b/tests/Service/GridFilterTest.php
@@ -0,0 +1,44 @@
+filter = new GridFilter();
+ }
+
+ public function testFilterField()
+ {
+ $this->filter->addFields(['foo','bar']);
+ $this->assertInstanceOf(FormFieldCollection::class,$this->filter->getFields());
+ }
+
+ public function testSetCssClass()
+ {
+ $this->filter->setCssClass('foo')
+ ->setCssClass('bar');
+ $this->assertEquals('foo bar',trim($this->filter->getCssClass()));
+ }
+
+ public function testGetData()
+ {
+ $this->assertEquals([],$this->filter->getData());
+
+ }
+
+ public function testAssignQueryData()
+ {
+ $this->filter->addFields(['foo','bar']);
+ $obj = $this->filter->assignQueryData();
+ $this->assertInstanceOf(GridFilter::class,$obj);
+ }
+}
diff --git a/tests/TestView.php b/tests/TestView.php
new file mode 100644
index 0000000..e4101a4
--- /dev/null
+++ b/tests/TestView.php
@@ -0,0 +1,144 @@
+view = $view;
+ $this->rendered = $view->render();
+ }
+
+ /**
+ * Assert that the given string is contained within the view.
+ *
+ * @param string $value
+ * @param bool $escaped
+ * @return $this
+ */
+ public function assertSee($value, $escaped = true)
+ {
+ $value = $escaped ? e($value) : $value;
+
+ PHPUnit::assertStringContainsString((string)$value, $this->rendered);
+
+ return $this;
+ }
+
+ /**
+ * Assert that the given strings are contained in order within the view.
+ *
+ * @param array $values
+ * @param bool $escape
+ * @return $this
+ */
+ public function assertSeeInOrder(array $values, $escape = true)
+ {
+ $values = $escape ? array_map('e', ($values)) : $values;
+
+ PHPUnit::assertThat($values, new SeeInOrder($this->rendered));
+
+ return $this;
+ }
+
+ /**
+ * Assert that the given string is contained within the view text.
+ *
+ * @param string $value
+ * @param bool $escape
+ * @return $this
+ */
+ public function assertSeeText($value, $escape = true)
+ {
+ $value = $escape ? e($value) : $value;
+
+ PHPUnit::assertStringContainsString((string)$value, strip_tags($this->rendered));
+
+ return $this;
+ }
+
+ /**
+ * Assert that the given strings are contained in order within the view text.
+ *
+ * @param array $values
+ * @param bool $escape
+ * @return $this
+ */
+ public function assertSeeTextInOrder(array $values, $escape = true)
+ {
+ $values = $escape ? array_map('e', ($values)) : $values;
+
+ PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->rendered)));
+
+ return $this;
+ }
+
+ /**
+ * Assert that the given string is not contained within the view.
+ *
+ * @param string $value
+ * @param bool $escape
+ * @return $this
+ */
+ public function assertDontSee($value, $escape = true)
+ {
+ $value = $escape ? e($value) : $value;
+
+ PHPUnit::assertStringNotContainsString((string)$value, $this->rendered);
+
+ return $this;
+ }
+
+ /**
+ * Assert that the given string is not contained within the view text.
+ *
+ * @param string $value
+ * @param bool $escape
+ * @return $this
+ */
+ public function assertDontSeeText($value, $escape = true)
+ {
+ $value = $escape ? e($value) : $value;
+
+ PHPUnit::assertStringNotContainsString((string)$value, strip_tags($this->rendered));
+
+ return $this;
+ }
+
+ /**
+ * Get the string contents of the rendered view.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->rendered;
+ }
+}
diff --git a/tests/Traits/InteractsWithViews.php b/tests/Traits/InteractsWithViews.php
new file mode 100644
index 0000000..dfecb02
--- /dev/null
+++ b/tests/Traits/InteractsWithViews.php
@@ -0,0 +1,79 @@
+getPaths())) {
+ ViewFacade::addLocation(sys_get_temp_dir());
+ }
+
+ $tempFile = tempnam($tempDirectory, 'laravel-blade') . '.blade.php';
+
+ file_put_contents($tempFile, $template);
+
+ return new TestView(view(Str::before(basename($tempFile), '.blade.php'), $data));
+ }
+
+ /**
+ * Render the given view component.
+ *
+ * @param string $componentClass
+ * @param array $data
+ * @return TestView
+ * @throws BindingResolutionException
+ */
+ protected function component(string $componentClass, array $data = [])
+ {
+ $component = $this->app->make($componentClass, $data);
+
+ $view = $component->resolveView();
+
+ return $view instanceof View
+ ? new TestView($view->with($component->data()))
+ : new TestView(view($view, $component->data()));
+ }
+
+ /**
+ * Populate the shared view error bag with the given errors.
+ *
+ * @param array $errors
+ * @param string $key
+ * @return void
+ */
+ protected function withViewErrors(array $errors, $key = 'default')
+ {
+ ViewFacade::share('errors', (new ViewErrorBag())->put($key, new MessageBag($errors)));
+ }
+}