git commit "add dynamic settings management"

This commit is contained in:
2026-08-20 13:12:51 +03:30
parent c04879b0d6
commit 598be63d71
17 changed files with 972 additions and 642 deletions
@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\SettingStoreRequest;
use App\Http\Requests\Admin\SettingUpdateRequest;
use App\Models\Setting;
class SettingController extends Controller
{
public function index()
{
$settings = Setting::query()
->applySearch()
->paginate(10);
return view('admin.settings.index', compact('settings'));
}
public function create()
{
$settings = Setting::all();
return view('admin.settings.create',compact('settings'));
}
public function store(SettingStoreRequest $request)
{
Setting::create($request->validated());
return redirect()->route('admin.settings.index');
}
public function edit(Setting $setting)
{
return view('admin.settings.edit', compact('setting'));
}
public function update(SettingUpdateRequest $request,Setting $setting)
{
$setting->update($request->validated());
return redirect()
->route('admin.settings.index')
->with('success', 'تنظیمات با موفقیت ویرایش شد.');
}
public function destroy(Setting $setting)
{
$setting->delete();
return redirect()
->route('admin.settings.index')
->with('success', 'محصول با موفقیت حذف شد.');
}
}
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class SettingStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'code'=>[
'required',
'string',
'max:100'
],
'value'=>[
'nullable',
'string',
]
];
}
}
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
class SettingUpdateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'code'=>[
'required',
'string',
'max:100'
],
'value'=>[
'nullable',
'string',
]
];
}
}
+18
View File
@@ -7,6 +7,8 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
@@ -29,4 +31,20 @@ class Setting extends Model
'code',
'value'
];
#[Scope]
protected function applySearch(Builder $query): void
{
$request = request();
if ($request->filled('keyword')) {
$keyword = $request->input('keyword');
$query->whereAny([
'code',
], 'LIKE', "%$keyword%");
}
}
}
+20
View File
@@ -1,5 +1,7 @@
<?php
use App\Models\Setting;
if (!function_exists('getUserFullName')) {
function getUserFullName(?\App\Models\User $user = null): string
{
@@ -22,4 +24,22 @@ if (!function_exists('getImageUrl')) {
}
}
if (!function_exists('settings')) {
function settings(?string $code): ?string
{
if (!$code) {
return null;
}
$setting = Setting::query()
->where('code', $code)
->first();
if (!$setting) {
return null;
}
return $setting->value;
}
}
+168
View File
@@ -0,0 +1,168 @@
.travel-footer {
margin-top:80px;
padding:70px 0 25px;
background:
linear-gradient(
135deg,
#06192f,
#0d5570
);
color:white;
}
.footer-logo {
font-size:32px;
font-weight:900;
color:#ffc107;
margin-bottom:20px;
}
.travel-footer p {
color:rgba(255,255,255,.75);
line-height:2;
}
.travel-footer h5 {
font-size:20px;
font-weight:800;
margin-bottom:25px;
}
.travel-footer ul {
list-style:none;
padding:0;
}
.travel-footer li {
margin-bottom:15px;
color:rgba(255,255,255,.75);
}
.travel-footer a {
color:rgba(255,255,255,.75);
text-decoration:none;
transition:.3s;
}
.travel-footer a:hover {
color:#ffc107;
padding-right:8px;
}
.footer-social {
display:flex;
gap:10px;
margin-top:25px;
}
.footer-social a {
border:1px solid rgba(255,255,255,.2);
padding:8px 15px;
border-radius:20px;
}
.footer-social a:hover {
background:#ffc107;
color:#06192f;
}
.footer-contact p {
margin-bottom:10px;
}
.footer-bottom {
display:flex;
justify-content:space-between;
align-items:center;
padding-top:20px;
color:rgba(255,255,255,.6);
}
@media(max-width:768px){
.footer-bottom{
flex-direction:column;
gap:10px;
text-align:center;
}
}
@@ -1,132 +0,0 @@
@extends('admin.layouts.app')
@section('content')
<div class="container mt-4">
<div class="card shadow">
<div class="card-body">
<h3 class="mb-4">
افزودن مدیر جدید
</h3>
<form action="{{ route('admin.admins.store') }}" method="POST">
@csrf
<div class="row">
<div class="col-md-6 mb-3">
<label>نام</label>
<input
type="text"
name="first_name"
class="form-control"
value="{{old('first_name')}}"
>
@error('first_name')
<small class="text-danger">
{{$message}}
</small>
@enderror
</div>
<div class="col-md-6 mb-3">
<label>نام خانوادگی</label>
<input
type="text"
name="last_name"
class="form-control"
value="{{old('last_name')}}"
>
</div>
<div class="col-md-6 mb-3">
<label>نام کاربری</label>
<input
type="text"
name="username"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>ایمیل</label>
<input
type="email"
name="email"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>موبایل</label>
<input
type="text"
name="mobile"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>رمز عبور</label>
<input
type="password"
name="password"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>وضعیت</label>
<select name="status" class="form-control">
<option value="1">
فعال
</option>
<option value="0">
غیرفعال
</option>
</select>
</div>
</div>
<button class="btn btn-success">
ذخیره
</button>
</form>
</div>
</div>
</div>
@endsection
-162
View File
@@ -1,162 +0,0 @@
@extends('admin.layouts.app')
@section('content')
<div class="container mt-4">
<div class="card shadow">
<div class="card-body">
<h3 class="mb-4">
ویرایش مدیر
</h3>
<div class="mb-3">
<label class="form-label">
نقش‌ها
</label>
@foreach($roles as $role)
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
name="roles[]"
value="{{ $role->id }}"
@checked(
$admin->roles->contains($role->id)
)
>
<label class="form-check-label">
{{ $role->name }}
</label>
</div>
@endforeach
</div>
<form action="{{ route('admin.admins.update', $admin->id) }}" method="POST">
@csrf
@method('PUT')
<div class="row">
<div class="col-md-6 mb-3">
<label>نام</label>
<input
type="text"
name="first_name"
class="form-control"
value="{{old('first_name')}}"
>
@error('first_name')
<small class="text-danger">
{{$message}}
</small>
@enderror
</div>
<div class="col-md-6 mb-3">
<label>نام خانوادگی</label>
<input
type="text"
name="last_name"
class="form-control"
value="{{old('last_name')}}"
>
</div>
<div class="col-md-6 mb-3">
<label>نام کاربری</label>
<input
type="text"
name="username"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>ایمیل</label>
<input
type="email"
name="email"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>موبایل</label>
<input
type="text"
name="mobile"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>رمز عبور</label>
<input
type="password"
name="password"
class="form-control"
>
</div>
<div class="col-md-6 mb-3">
<label>وضعیت</label>
<select name="status" class="form-control">
<option value="1">
فعال
</option>
<option value="0">
غیرفعال
</option>
</select>
</div>
</div>
<button class="btn btn-success">
ذخیره
</button>
</form>
</div>
</div>
</div>
@endsection
@@ -1,209 +0,0 @@
@extends('admin.layouts.app')
@section('breadcrumb')
<span>
مدیریت مدیران
</span>
@endsection
@section('content')
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">
لیست مدیران
</h5>
<a href="{{ route('admin.admins.create') }}" class="btn btn-primary">
افزودن مدیر جدید
</a>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body">
<form method="GET" action="{{ route('admin.admins.index') }}">
<div class="row g-2">
<div class="col-md-5">
<input
type="text"
name="keyword"
value="{{ request('keyword') }}"
class="form-control"
placeholder="جستجو بر اساس نام..."
>
</div>
<div class="col-md-3">
<select
class="form-control"
name="sort"
onchange="this.form.submit()"
>
<option value="">
مرتب‌سازی بر اساس
</option>
<option value="first_name_asc"
{{ request('sort') == 'first_name_asc' ? 'selected' : '' }}>
نام (الف - ی)
</option>
<option value="first_name_desc"
{{ request('sort') == 'first_name_desc' ? 'selected' : '' }}>
نام (ی - الف)
</option>
<option value="newest"
{{ request('sort') == 'newest' ? 'selected' : '' }}>
جدیدترین
</option>
</select>
</div>
<div class="col-md-2">
<button class="btn btn-primary w-100">
جستجو
</button>
</div>
</div>
</form>
</div>
</div>
<div class="card shadow">
<div class="card-body">
<div class="table-responsive">
<table class="table table-bordered table-hover text-center align-middle">
<thead class="table-light">
<tr>
<th>شناسه</th>
<th>نام</th>
<th>نام خانوادگی</th>
<th>نام کاربری</th>
<th>ایمیل</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
@forelse($admins as $admin)
<tr>
<td>{{ $admin->id }}</td>
<td>{{ $admin->first_name }}</td>
<td>{{ $admin->last_name }}</td>
<td>{{ $admin->username }}</td>
<td>{{ $admin->email }}</td>
<td>
@if($admin->status)
<span class="badge bg-success">
فعال
</span>
@else
<span class="badge bg-danger">
غیرفعال
</span>
@endif
</td>
<td>
<a href="{{ route('admin.admins.edit', $admin->id) }}"
class="btn btn-sm btn-warning">
ویرایش
</a>
<form action="{{ route('admin.admins.destroy', $admin->id) }}"
method="POST"
class="d-inline">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger"
onclick="return confirm('آیا مطمئن هستید؟')">
حذف
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7">
رکوردی پیدا نشد
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{ $admins->links() }}
</div>
</div>
</div>
@endsection
@@ -96,6 +96,14 @@
مدیریت شهرستان ها </a>
</li>
<li class="nav-item mb-2">
<a href="{{ route('admin.settings.index') }}"
class="nav-link text-white">
تنظیمات </a>
</li>
@@ -1,3 +0,0 @@
<div>
<!-- He who is contented is rich. - Laozi -->
</div>
@@ -0,0 +1,136 @@
@extends('admin.layouts.app')
@section('breadcrumb')
<div>
<h1 class="page-title fw-medium fs-18 mb-2">
ایجاد تنظیم
</h1>
<nav>
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="javascript:void(0)">
مدیریت تنظیمات
</a>
</li>
<li class="breadcrumb-item active">
ایجاد تنظیم
</li>
</ol>
</nav>
</div>
@endsection
@section('content')
<div class="row">
<div class="col-xl-12">
<form action="{{ route('admin.settings.store') }}"
method="POST">
@csrf
<div class="card custom-card">
<div class="card-header">
<div class="card-title">
ایجاد تنظیم
</div>
</div>
<div class="card-body">
<div class="row gy-4">
<div class="col-xl-6">
<label class="form-label">
کلید تنظیم
</label>
<input
type="text"
class="form-control"
name="code"
placeholder="مثلا site_name"
value="{{ old('code') }}"
>
@error('code')
<span class="text-danger">
{{ $message }}
</span>
@enderror
</div>
<div class="col-xl-6">
<label class="form-label">
مقدار
</label>
<input
type="text"
class="form-control"
name="value"
placeholder="مقدار تنظیم"
value="{{ old('value') }}"
>
@error('value')
<span class="text-danger">
{{ $message }}
</span>
@enderror
</div>
</div>
</div>
<div class="card-footer text-end">
<button type="submit"
class="btn btn-primary">
ایجاد تنظیم
</button>
</div>
</div>
</form>
</div>
</div>
@endsection
@@ -0,0 +1,144 @@
@extends('admin.layouts.app')
@section('breadcrumb')
<div>
<h1 class="page-title fw-medium fs-18 mb-2">
ویرایش تنظیم
</h1>
<nav>
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="javascript:void(0)">
مدیریت تنظیمات
</a>
</li>
<li class="breadcrumb-item active">
ویرایش تنظیم
</li>
</ol>
</nav>
</div>
@endsection
@section('content')
<div class="main-content app-content">
<div class="container-fluid pt-4">
<div class="row">
<div class="col-xl-12">
<form action="{{ route('admin.settings.update',$setting->id) }}"
method="POST">
@csrf
@method('PUT')
<div class="card custom-card">
<div class="card-header">
<div class="card-title">
ویرایش تنظیم
</div>
</div>
<div class="card-body">
<div class="row gy-4">
<div class="col-xl-6">
<label class="form-label">
کلید تنظیم
</label>
<input
type="text"
class="form-control"
name="code"
placeholder="مثلا site_name"
value="{{ old('code',$setting->key) }}"
>
@error('code')
<span class="text-danger">
{{ $message }}
</span>
@enderror
</div>
<div class="col-xl-6">
<label class="form-label">
مقدار
</label>
<input
type="text"
class="form-control"
name="value"
placeholder="مقدار تنظیم"
value="{{ old('value',$setting->value) }}"
>
@error('value')
<span class="text-danger">
{{ $message }}
</span>
@enderror
</div>
</div>
</div>
<div class="card-footer text-end">
<button type="submit"
class="btn btn-primary">
ذخیره تغییرات
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,200 @@
@extends('admin.layouts.app')
@section('breadcrumb')
<div>
<h1 class="page-title fw-medium fs-18 mb-2">
لیست تنظیمات
</h1>
<nav>
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="javascript:void(0)">
مدیریت تنظیمات
</a>
</li>
<li class="breadcrumb-item active">
لیست تنظیمات
</li>
</ol>
</nav>
</div>
@endsection
@section('content')
<div class="card custom-card">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="card-title">
لیست تنظیمات
</div>
<a href="{{ route('admin.settings.create') }}"
class="btn btn-primary">
ایجاد تنظیمات
</a>
</div>
<div class="card-body">
<form method="GET" class="mb-4">
<div class="row g-2 align-items-center">
<div class="col-md-5">
<input
type="text"
name="keyword"
class="form-control"
placeholder="جستجو تنظیمات..."
value="{{ request('keyword') }}"
>
</div>
<div class="col-md-2">
<button class="btn btn-primary w-100"
type="submit">
جستجو
</button>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-bordered table-hover text-center align-middle">
<thead class="table-light">
<tr>
<th>
کلید تنظیم
</th>
<th>
مقدار
</th>
<th>
تاریخ ثبت
</th>
<th>
عملیات
</th>
</tr>
</thead>
<tbody>
@forelse($settings as $setting)
<tr>
<td>
{{ $setting->code }}
</td>
<td>
{{ $setting->value }}
</td>
<td>
{{ $setting->created_at->toJalali()->format('Y/m/d') }}
</td>
<td>
<a href="{{ route('admin.settings.edit',$setting->id) }}"
class="btn btn-warning btn-sm">
ویرایش
</a>
<form action="{{ route('admin.settings.destroy',$setting->id) }}"
method="POST"
class="d-inline">
@csrf
@method('DELETE')
<button class="btn btn-danger btn-sm">
حذف
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="4">
تنظیماتی ثبت نشده است.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-3">
{{ $settings->links() }}
</div>
</div>
</div>
@endsection
+1
View File
@@ -12,6 +12,7 @@
<link rel="stylesheet" href="{{ asset('assets/css/styles.css?v=2') }}">
<link rel="stylesheet" href="{{ asset('assets/css/flight_tour.css') }}">
<link rel="stylesheet" href="{{ asset('assets/css/header.css') }}">
<link rel="stylesheet" href="{{ asset('assets/css/footer.css') }}">
</head>
<body>
+123 -135
View File
@@ -1,150 +1,138 @@
<div class="theme-footer" id="mainFooter">
<div class="container _ph-mob-0">
<div class="row row-eq-height row-mob-full" data-gutter="60">
<div class="col-md-3">
<div class="theme-footer-section theme-footer-">
<a class="theme-footer-brand _mb-mob-30" href="#">
<img src="img/logo-black.png" alt="تصویر متن جایگزین" title="عنوان تصویر">
</a>
<div class="theme-footer-brand-text">
<p>اما بچه های حاضر هم در غم المپیک متولد نمی شوند</p>
<p>کمان در گلو نیست، بنابراین سن نخبگان نیست</p>
</div>
</div>
</div>
<div class="col-md-5">
<div class="row">
<div class="col-md-4">
<div class="theme-footer-section theme-footer-">
<h5 class="theme-footer-section-title">همسفر</h5>
<ul class="theme-footer-section-list">
<li>
<a href="#">درباره تراول میت</a>
</li>
<li>
<a href="#">اپلیکیشن موبایل</a>
</li>
<li>
<a href="#">پشتیبانی مشتری</a>
</li>
<li>
<a href="#">تبلیغات</a>
</li>
<li>
<a href="#">شغل ها</a>
</li>
<li>
<a href="#">سیاست حفظ حریم خصوصی</a>
</li>
<li>
<a href="#">شرایط استفاده</a>
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="theme-footer-section theme-footer-">
<h5 class="theme-footer-section-title">سفر کنید</h5>
<ul class="theme-footer-section-list">
<li>
<a href="#">کشورها</a>
</li>
<li>
<a href="#">مناطق</a>
</li>
<li>
<a href="#">شهرها</a>
</li>
<li>
<a href="#">نواحی</a>
</li>
<li>
<a href="#">فرودگاه ها</a>
</li>
<li>
<a href="#">هتل</a>
</li>
<li>
<a href="#">مکان های مورد علاقه</a>
</li>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="theme-footer-section theme-footer-">
<h5 class="theme-footer-section-title">کتاب</h5>
<ul class="theme-footer-section-list">
<li>
<a href="#">آپارتمان ها</a>
</li>
<li>
<a href="#">استراحتگاه ها</a>
</li>
<li>
<a href="#">ویلاها</a>
</li>
<li>
<a href="#">خوابگاه ها</a>
</li>
<li>
<a href="#">رستوران</a>
</li>
<li>
<a href="#">مهمانخانه</a>
</li>
<li>
<a href="#">هتل های زنجیره ای</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="theme-footer-section theme-footer-section-subscribe bg-grad _mt-mob-30">
<div class="theme-footer-section-subscribe-bg" style="background-image:url(img/footer/footer_subscribe_bg.png);"></div>
<div class="theme-footer-section-subscribe-content">
<h5 class="theme-footer-section-title">تا 50 درصد از سفر بعدی خود صرفه جویی کنید</h5>
<p class="text-muted">برای باز کردن قفل معاملات مخفی ما مشترک شوید</p>
<form>
<div class="form-group">
<input class="form-control theme-footer-subscribe-form-control" type="email" placeholder="ایمیل خود را اینجا تایپ کنید">
</div>
<button class="btn btn-primary-invert btn-shadow text-upcase theme-footer-subscribe-btn" type="submit">معاملات دریافت کنید</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="theme-copyright">
<footer class="travel-footer">
<div class="container">
<div class="row">
<div class="col-md-6">
<p class="theme-copyright-text">حق چاپ © 1400
<a href="#">Bookify</a> . تمامی حقوق محفوظ است.
<div class="row gy-5">
<div class="col-lg-4 col-md-6">
<h3 class="footer-logo">
✈ Airline Agency
</h3>
<p>
تجربه‌ای متفاوت از سفر با بهترین پروازها،
تورهای جذاب و خدمات گردشگری حرفه‌ای.
</p>
</div>
<div class="col-md-6">
<ul class="theme-copyright-social">
<div class="col-lg-2 col-md-6">
<h5>
دسترسی سریع
</h5>
<ul>
<li>
<a class="fa fa-facebook" href="#"></a>
<a href="/">
خانه
</a>
</li>
<li>
<a class="fa fa-google" href="#"></a>
<a href="/flights">
پروازها
</a>
</li>
<li>
<a class="fa fa-twitter" href="#"></a>
<a href="/tours">
تورها
</a>
</li>
<li>
<a class="fa fa-youtube-play" href="#"></a>
</li>
<li>
<a class="fa fa-instagram" href="#"></a>
<a href="#">
تماس با ما
</a>
</li>
</ul>
</div>
<div class="col-lg-3 col-md-6">
<h5>
خدمات ما
</h5>
<ul>
<li>
رزرو آنلاین پرواز
</li>
<li>
رزرو تور داخلی و خارجی
</li>
<li>
پشتیبانی ۲۴ ساعته
</li>
<li>
پرداخت امن
</li>
</ul>
</div>
<div class="col-lg-3 col-md-6">
<h5>
ارتباط با ما
</h5>
<div class="footer-contact">
<p>
<a href="tel:{{settings('phone')}}"></a>
{{settings('phone')}}
</p>
<p>
{{settings('address')}}
</p>
</div>
</div>
</div>
<hr>
<div class="footer-bottom">
<span>
© {{ date('Y') }} تمامی حقوق محفوظ است
</span>
<span>
طراحی شده با ❤️ برای سفر شما
</span>
</div>
</div>
</div>
</footer>
+18
View File
@@ -8,6 +8,7 @@ use App\Http\Controllers\Admin\Auth\DashboardController;
use App\Http\Controllers\Admin\FlightController;
use App\Http\Controllers\Admin\ProvinceController;
use App\Http\Controllers\Admin\RoleController;
use App\Http\Controllers\Admin\SettingController;
use App\Http\Controllers\Admin\UserController;
use App\Http\Controllers\Admin\TourController;
use App\Http\Controllers\Admin\CityController;
@@ -138,6 +139,23 @@ Route::prefix('admin')->name('admin.')->group(function (){
Route::delete('destroy', 'destroy')->name('destroy');
});
});
Route::prefix('settings')->name('settings.')->controller(SettingController::class)->group(function (){
Route::get('/','index')->name('index');
Route::get('create','create')->name('create');
Route::post('store','store')->name('store');
Route::prefix('{setting}')->group(function (){
Route::get('edit','edit')->name('edit');
Route::put('update','update')->name('update');
Route::delete('destroy','destroy')->name('destroy');
});
});
});
});