added panel list component

fixed post group id
pull/44/head
A1Gard 7 months ago
parent 5bdd998c54
commit 5a3deaa61b

@ -1,15 +1,15 @@
<?php
namespace App\Helpers;
use App\Helpers;
/**
* @param $lang code like fa
* @return string
*/
function getEmojiLanguagebyCode($lang) {
$languages = array(
function getEmojiLanguagebyCode($lang)
{
$languages = [
"af" => "🇿🇦", // Afrikaans
"sq" => "🇦🇱", // Albanian
"am" => "🇪🇹", // Amharic
@ -76,7 +76,7 @@ function getEmojiLanguagebyCode($lang) {
"uz" => "🇺🇿", // Uzbek
"vi" => "🇻🇳", // Vietnamese
"cy" => "🇬🇧" // Welsh
);
];
$lang = strtolower($lang);
if (array_key_exists($lang, $languages)) {
return $languages[$lang];
@ -84,3 +84,55 @@ function getEmojiLanguagebyCode($lang) {
return "❓";
}
}
/**
* has route as named we want this model?
* @param $name string
* @return bool
*/
function hasRoute($name)
{
// create route
$cRuote = str_replace('index', $name, request()->route()->getName());
if (\Illuminate\Support\Facades\Route::has($cRuote)) {
return true;
} else {
return false;
}
}
/**
* get named route url
* @param $name string
* @param $args array
* @return string|null
*/
function getRoute($name,$args = [])
{
// create route
$cRuote = str_replace('index', $name, request()->route()->getName());
if (\Illuminate\Support\Facades\Route::has($cRuote)) {
return \route($cRuote,$args);
} else {
return null;
}
}
/**
* make sort link suffix
* @param $col string
* @return string
*/
function sortSuffix($col){
if (request()->sort == $col){
if (request('sortType','asc') == 'desc'){
return '&sortType=asc';
}else{
return '&sortType=desc';
}
}else{
return '';
}
}

@ -1,6 +1,7 @@
<?php
namespace App\Helpers;
class TDate
{

@ -5,9 +5,10 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Controllers\XController;
use Illuminate\Http\Request;
use App\Helper;
use function App\Helpers\hasCreateRoute;
class UserController extends XController
{
//
protected $cols = ['name','email','role','mobile'];
}

@ -10,21 +10,43 @@ class XController extends Controller
protected $model = User::class;
protected $name = "User";
protected $cols = [];
protected $extra_cols = ['id'];
protected $listView = 'admin.users.user-list';
protected $formView = 'admin.users.user-form';
public function createOrUpdate($item, Request $request) {
public function createOrUpdate($item, Request $request)
{
}
protected function showList($query)
{
$items = $query->paginate(config('app.panel.page_count'), array_merge($this->extra_cols, $this->cols));
$cols = $this->cols;
return view($this->listView, compact('items', 'cols'));
}
protected function makeSortAndFilter()
{
if (!\request()->has('sort') || !in_array(\request('sort'), $this->cols)) {
$query = $this->model::orderByDesc('id');
} else {
$query = $this->model::orderBy(\request('sort'), \request('sortType', 'asc'));
}
return $query;
}
/**
* Display a listing of the resource.
*/
public function index()
{
//
$items = $this->model::orderByDesc('id')->paginate(config('app.panel.page_count'));
return view($this->listView,compact('items'));
$query = $this->makeSortAndFilter();
return $this->showList($query);
}
/**
@ -47,7 +69,7 @@ class XController extends Controller
/**
* Display the specified resource.
*/
public function show( $user)
public function show($user)
{
//
}
@ -63,7 +85,7 @@ class XController extends Controller
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $user)
public function update(Request $request, $user)
{
//
}
@ -75,4 +97,14 @@ class XController extends Controller
{
//
}
/**
* Show list of trashed
*/
public function trashed()
{
$query = User::onlyTrashed();
return $this->showList($query);
}
}

@ -13,5 +13,5 @@ class Menu extends Model
public function menuItems()
{
return $this->hasMany(MenuItem::class, 'menu_id', 'id');
};
}
}

@ -89,16 +89,16 @@ class Post extends Model implements HasMedia
return $this->morphMany(Comment::class, 'commentable')->where('status', 1);
}
public function toArray()
{
return [
'id' => $this->id,
'title' => $this->title,
'subtitle' => $this->subtitle,
'body' => $this->body,
'categories' => $this->categories->implode(' ') ?? null,
'author' => $this->author->name ?? null,
'tags' => $this->tags->implode(' ') ?? null,
];
}
// public function toArray()
// {
// return [
// 'id' => $this->id,
// 'title' => $this->title,
// 'subtitle' => $this->subtitle,
// 'body' => $this->body,
// 'categories' => $this->categories->implode(' ') ?? null,
// 'author' => $this->author->name ?? null,
// 'tags' => $this->tags->implode(' ') ?? null,
// ];
// }
}

@ -4,13 +4,14 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasFactory, Notifiable, HasRoles;
use HasFactory, Notifiable, HasRoles, SoftDeletes;
static $roles = ['DEVELOPER', 'ADMIN', 'USER'];
@ -47,4 +48,8 @@ class User extends Authenticatable
'password' => 'hashed',
];
}
public function posts(){
return $this->hasMany(Post::class);
}
}

@ -1,11 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class user extends Model
{
use HasFactory;
}

@ -1,11 +1,11 @@
<?php
namespace App\Providers;
use App\Helpers\TDate;
use App\Http\Middleware\Acl;
use Carbon\Carbon;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -32,5 +32,7 @@ class AppServiceProvider extends ServiceProvider
$dt = TDate::GetInstance();
return $dt->PDate($format, self::this()->timestamp);
});
}
}

@ -0,0 +1,38 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class BladeServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
//
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
Blade::directive('paginated', function ($expression) {
return "<?php
\$items = $expression;
\$total = \$items->total();
\$currentPage = \$items->currentPage();
\$perPage = \$items->perPage();
\$from = (\$currentPage - 1) * \$perPage + 1;
\$to = min(\$currentPage * \$perPage, \$total);
echo \"(\$from | \$to | \$total) \"; ?>";
});
}
}

@ -2,5 +2,6 @@
return [
App\Providers\AppServiceProvider::class,
App\Providers\BladeServiceProvider::class,
Spatie\Permission\PermissionServiceProvider::class,
];

@ -34,15 +34,14 @@
"spatie/laravel-ignition": "^2.4"
},
"autoload": {
"files": [
"app/Helpers/Helper.php",
"app/Helpers/TDate.php"
],
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"files": [
"app/Helpers/Helper.php"
]
},
"autoload-dev": {
"psr-4": {

@ -1,4 +1,6 @@
<?php
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\ServiceProvider;
return [
@ -148,4 +150,35 @@ return [
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => Facade::defaultAliases()->merge([
// 'Example' => App\Facades\Example::class,
])->toArray(),
'providers' => ServiceProvider::defaultProviders()->merge([
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
Translator\Framework\TranslatorServiceProvider::class,
\App\Providers\BladeServiceProvider::class,
])->toArray(),
];

@ -27,6 +27,8 @@ class UserFactory extends Factory
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'mobile' => '0912'.rand(1000000,9999999),
'role' => 'USER',
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];

@ -22,6 +22,7 @@ return new class extends Migration
$table->enum('role',\App\Models\User::$roles)->default('USER');
$table->rememberToken();
$table->timestamps();
$table->softDeletes();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {

@ -17,7 +17,7 @@ return new class extends Migration
$table->string('slug')->unique();
$table->string('subtitle', 4096);
$table->text('body');
$table->unsignedBigInteger('category_id');
$table->unsignedBigInteger('group_id');
$table->unsignedBigInteger('user_id');
$table->unsignedTinyInteger('status')->default(0);
$table->boolean('is_breaking')->default(0);
@ -31,7 +31,8 @@ return new class extends Migration
$table->foreign('user_id')
->references('id')->on('users');
$table->foreign('category_id')
$table->foreign('group_id')
->references('id')->on('groups');
});

@ -1,10 +1,8 @@
<?php
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class DatabaseSeeder extends Seeder
{

@ -57,5 +57,7 @@ class UserSeeder extends Seeder
$user->assignRole('user');
$user->save();
User::factory(50)->create();
}
}

@ -16,6 +16,7 @@ import './bootstrap';
import { createApp } from 'vue';
import './panel/raw.js';
import './panel/navbar.js';
import './panel/list-checkboxs.js';
/**
* Next, we will create a fresh Vue application instance. You may then begin

@ -0,0 +1,88 @@
function clearSelection()
{
if (window.getSelection) {window.getSelection().removeAllRanges();}
else if (document.selection) {document.selection.empty();}
}
window.addEventListener('load',function () {
let chkall = document.querySelectorAll(".chkall");
document.querySelector('#toggle-select').addEventListener('click',function () {
let checkboxes = document.querySelectorAll(".chkbox");
checkboxes.forEach(function(checkbox) {
if (!checkbox.checked){
checkbox.checked = true;
checkbox.setAttribute("checked", "");
}else{
checkbox.checked = false;
checkbox.removeAttribute("checked");
}
});
});
// Attach an event listener for "change" and "click" events
chkall.forEach(function(chkall) {
chkall.addEventListener("change", handleCheckboxChange);
chkall.addEventListener("click", handleCheckboxChange);
});
function handleCheckboxChange() {
let isChecked = this.checked;
let table = this.closest("table");
if (isChecked) {
// Check all checkboxes in the table
let checkboxes = table.querySelectorAll(".chkbox");
checkboxes.forEach(function(checkbox) {
checkbox.checked = true;
checkbox.setAttribute("checked", "");
});
} else {
// Uncheck all checkboxes in the table
let checkboxes = table.querySelectorAll(".chkbox");
checkboxes.forEach(function(checkbox) {
checkbox.checked = false;
checkbox.removeAttribute("checked");
});
}
}
// select with shift button
const chkboxes = document.querySelectorAll('.chkbox');
let lastChecked = null;
chkboxes.forEach(chkbox => {
chkbox.addEventListener('click', handleCheckboxClick);
chkbox.parentNode.querySelector('label').addEventListener('click', handleCheckboxClick);
});
function handleCheckboxClick(e) {
clearSelection();
let self = this;
if (e.target.tagName === 'LABEL'){
self = e.target.parentNode.querySelector('input');
}
if (!lastChecked) {
lastChecked = self;
return;
}
if (e.shiftKey) {
const start = Array.from(chkboxes).indexOf(self);
const end = Array.from(chkboxes).indexOf(lastChecked);
const range = Array.from(chkboxes).slice(Math.min(start, end) + 1, Math.max(start, end) );
range.forEach(chkbox => {
chkbox.checked = lastChecked.checked;
});
}
lastChecked = self;
}
});

@ -1,20 +0,0 @@
.circle-btn{
width: 75px;
height: 75px;
border-radius: 50%;
background: rgba(139, 0, 139, 0.86);
backdrop-filter: blur(4px);
border: 0;
box-shadow: 0px 3px 5px #11111177, inset 0 0 0 0 darkred;
user-select: none;
cursor: pointer;
transition: 500ms;
&:hover{
box-shadow: 0px 3px 5px #11111177, inset 0 0 0 40px darkred;
}
}
.custom-tooltip {
--bs-tooltip-bg: var(--bs-indigo);
--bs-tooltip-color: var(--bs-white);
}

@ -2,7 +2,7 @@
@import 'vazirmatn/Vazirmatn-font-face.css';
@import "remixicon/fonts/remixicon.css";
// Variables
@import 'variables';
@import 'panel/variables';
// Bootstrap
@import 'bootstrap/scss/bootstrap';
@ -47,7 +47,7 @@ a{
>aside{
background-image: url("../images/pattern.png");
background-color: #282c34;
background-color: lighten($body-bg,7);
}
&.sided{
grid-template-columns:50px 250px 1fr;
@ -58,7 +58,9 @@ a{
}
@import "common";
@import "raw";
@import "fix";
@import "navbar";
@import "panel/common";
@import "panel/raw";
@import "panel/fix";
@import "panel/navbar";
@import "panel/breadcrumbs";
@import "panel/item-list";

@ -0,0 +1,5 @@
#panel-breadcrumb{
height: 50px;
background: #00000033;
margin-bottom: 1rem;
}

@ -0,0 +1,53 @@
body{
background: $body-bg;
}
.circle-btn{
width: 75px;
height: 75px;
border-radius: 50%;
background: $secondary-color-panel;
backdrop-filter: blur(4px);
border: 0;
box-shadow: 0px 3px 5px #11111177, inset 0 0 0 0 darkred;
user-select: none;
cursor: pointer;
transition: 500ms;
&:hover{
box-shadow: 0px 3px 5px #11111177, inset 0 0 0 40px darkred;
}
}
.custom-tooltip {
--bs-tooltip-bg: var(--bs-indigo);
--bs-tooltip-color: var(--bs-white);
}
.grid-equal {
display: grid;
grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column;
}
a.btn,a.action-btn,a.circle-btn{
&:hover{
color:white;
i{
color: white;
}
}
}
.action-btn{
display: block;
font-size: 47px;
text-align: center;
i{
color: white;
}
position: fixed;
inset-inline-end: 1rem;
bottom: 1rem;
}

@ -5,7 +5,7 @@ border-color: #bc86fe;
.card{
background-color: rgba(var(--bs-body-bg),.5);
background-image: url("../images/pattern.png");
background-image: url("../../images/pattern.png");
backdrop-filter: blur(4px);
box-shadow: -2px -2px 7px #560000, 2px 2px 7px #5e00b5;
}
@ -27,3 +27,10 @@ a,a:visited{
color: #ff0067;
}
}
.pagination{
--bs-pagination-active-bg: #6e0000 ;
--bs-pagination-active-border-color: #810000;
--bs-pagination-color: #cb00a5;
--bs-pagination-hover-color: red;
}

@ -0,0 +1,86 @@
.item-list {
overflow: hidden;
background: $lighter-color;
//margin: 0 1rem;
border-radius: 4px;
h1 {
font-size: 25px;
padding: 1rem;
font-weight: 200;
margin: 0;
}
.item-search {
form {
padding: 1rem;
}
}
}
.table-list {
width: 100%;
tr{
th{
background-color: #00000033;
background-image: url("../images/pattern.png");
}
td,th{
&:first-child{
width: 100px;
}
&:last-child{
text-align: center;
}
vertical-align: center;
padding: .5rem;
border-bottom: 1px solid #ffffff11;
input{
margin: .5rem 1rem ;
}
a{
b{
color: white;
}
}
}
td{
label{
padding: .45rem 1rem ;
cursor: pointer;
border-radius: 3px;
}
input:checked + label{
background: lighten($primary-color-panel,10);
}
input{
display: none;
}
}
&:nth-child(even) td{
background: #00000010;
}
&:hover{
td{
background-color: #00000044;
background-image: url("../images/pattern.png");
}
}
}
tfoot th{
border-bottom: 0;
ul.pagination{
margin: 0;
justify-content: center;
}
}
}

@ -4,6 +4,7 @@
justify-content: center;
align-items: center;
width: 100%;
position: relative;
#raw-form{
max-width: 95%;
width: 450px;

@ -1,8 +1,10 @@
// Body
$body-bg: #f8fafc;
$body-bg: #1b223a;
// Typography
$font-family-sans-serif: 'Vazirmatn', sans-serif;
$font-size-base: 0.9rem;
$line-height-base: 1.6;
$primary-color-panel: #6e0000;
$secondary-color-panel: rgba(139, 0, 139, 0.86) ;
$lighter-color: #282d47;

@ -0,0 +1,192 @@
@extends('layouts.app')
@section('content')
<div class="mb-5 pb-5">
<div class="row">
{{-- list side bar start--}}
<div class="col-xl-3 mb-3">
<div class="item-list">
<div class="row">
<div class="col-8">
<h1>
@yield('list-title')
</h1>
</div>
<div class="col-4 pt-3 text-end">
@if(hasRoute('trashed'))
<a class="btn btn-outline-danger me-2"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Trashed items")}}"
href="{{getRoute('trashed')}}"
>
<i class="ri-delete-bin-6-line"></i>
</a>
@endif
</div>
</div>
<form action="" class="p-2">
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="{{__("Search")}}..."
aria-label="{{__("Search")}}..." aria-describedby="button-addon2">
<button class="btn btn-outline-secondary" type="button" id="button-addon2">
<i class="ri-search-2-line"></i>
</button>
</div>
@yield('filter')
</form>
</div>
</div>
{{-- list side bar end--}}
{{-- list content start--}}
<div class="col-xl-9">
<div class="item-list">
<table class="table-list">
<thead>
<tr>
<th>
<div
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Check all")}}"
class="form-check form-switch mt-1 mx-2">
<input class="form-check-input chkall"
type="checkbox" role="switch">
</div>
</th>
@foreach($cols as $col)
<th>
<a href="?sort={{$col}}{{sortSuffix($col)}}">
{{__($col)}}
</a>
</th>
@endforeach
@yield('table-head')
<th class="text-center">
{{__("Totol")}}:
{{$items->total()}}
</th>
</tr>
</thead>
<tbody>
@foreach($items as $item)
<tr>
<td>
<input type="checkbox" id="chk-{{$item->id}}" class="chkbox"
name="id[{{$item->id}}]">
<label for="chk-{{$item->id}}">
{{$item->id}}
</label>
</td>
@foreach($cols as $k => $col)
@if($k == 0 && hasRoute('edit'))
<td>
<a href="{{getRoute('edit',$item->id)}}">
<b>
{{$item->name}}
</b>
</a>
</td>
@else
<td>
{{$item->$col}}
</td>
@endif
@endforeach
@yield('table-body')
<td>
@if(hasRoute('destroy'))
<a href="{{getRoute('destroy',$item->id)}}"
class="btn btn-outline-danger btn-sm mx-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Remove")}}">
<i class="ri-close-line"></i>
</a>
@endif
@if(hasRoute('edit'))
<a href="{{getRoute('edit',$item->id)}}"
class="btn btn-outline-primary btn-sm mx-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Edit")}}">
<i class="ri-edit-2-line"></i>
</a>
@endif
@if(hasRoute('show'))
<a href="{{getRoute('show',$item->id)}}"
class="btn btn-outline-secondary btn-sm mx-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Show")}}">
<i class="ri-eye-line"></i>
</a>
@endif
@yield('list-btn')
</td>
</tr>
@endforeach
</tbody>
{{-- pagination and toggle button start --}}
<tfoot>
<tr>
<th colspan="100%">
<div class="row">
<div class="col-md-3 text-start">
<div
id="toggle-select"
class="btn btn-outline-light mx-2"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Toggle selection")}}">
<i class="ri-toggle-line"></i>
</div>
</div>
<div class="col-md-6">
{{$items->withQueryString()->links()}}
</div>
<div class="col-md-3 text-center">
<div class="p-2" data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="({{__("From - To - Total")}})">
@paginated($items)
</div>
</div>
</div>
</th>
</tr>
</tfoot>
{{-- pagination and toggle button end --}}
</table>
</div>
</div>
{{-- list content end--}}
</div>
</div>
@if(hasRoute('create'))
<a class="action-btn circle-btn"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Add another one")}}"
href="{{getRoute('create')}}"
>
<i class="ri-add-line"></i>
</a>
@endif
@endsection

@ -1 +1,12 @@
<?php
@extends('admin.templates.panel-list-template')
@section('list-title')
<i class="ri-user-3-line"></i>
{{__("Users list")}}
@endsection
@section('title')
{{__("Users list")}} |
@endsection
@section('filter')
@endsection

@ -0,0 +1,3 @@
<nav id="panel-breadcrumb">
1
</nav>

@ -183,7 +183,7 @@
</a>
<ul id="manage">
<li>
<a>
<a href="{{route('admin.user.index')}}">
<i class="ri-folder-user-fill"></i>
{{__("Users")}}
</a>

@ -9,7 +9,8 @@
@include('components.panel-side-navbar')
</aside>
<div id="sidebar-panel"></div>
<main class="py-4">
<main class="py-3 px-3">
@include('components.panel-breadcrumb')
@yield('content')
</main>
</div>

@ -21,9 +21,11 @@ Route::prefix(config('app.panel.prefix'))->name('admin.')->group(
Route::get('create', [\App\Http\Controllers\Admin\UserController::class, 'create'])->name('create');
Route::post('store', [\App\Http\Controllers\Admin\UserController::class, 'store'])->name('store');
Route::get('edit/{user}', [\App\Http\Controllers\Admin\UserController::class, 'edit'])->name('edit');
Route::get('show/{user}', [\App\Http\Controllers\Admin\UserController::class, 'show'])->name('show');
Route::post('update/{user}', [\App\Http\Controllers\Admin\UserController::class, 'update'])->name('update');
Route::get('delete/{user}', [\App\Http\Controllers\Admin\UserController::class, 'destroy'])->name('delete');
Route::get('delete/{user}', [\App\Http\Controllers\Admin\UserController::class, 'destroy'])->name('destroy');
Route::post('bulk', [\App\Http\Controllers\Admin\UserController::class, "bulk"])->name('bulk');
Route::get('trashed', [\App\Http\Controllers\Admin\UserController::class, "trashed"])->name('trashed');
});
});
});

Loading…
Cancel
Save