feat: Implement Counselor management with CRUD functionality and associated views

This commit is contained in:
2025-08-19 17:28:23 +05:45
parent 3262e279a8
commit 6f16c1230f
12 changed files with 369 additions and 12 deletions

View File

@@ -0,0 +1,117 @@
<?php
namespace Modules\CCMS\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Modules\CCMS\Models\Counselor;
use Yajra\DataTables\Facades\DataTables;
use App\Rules\Recaptcha;
use Illuminate\Support\Facades\Validator;
class CounselorController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
if (request()->ajax()) {
$model = Counselor::query()->latest();
return DataTables::eloquent($model)
->addIndexColumn()
->addColumn('action', 'ccms::counselor.datatable.action')
->rawColumns(['action'])
->toJson();
}
return view('ccms::counselor.index', [
'title' => 'Counselor List',
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('ccms::create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
$rules = [
'name' => 'required|string',
'email' => 'required|email',
'address' => 'required|string',
'contact' => 'required|string',
'test_score' => 'required|string',
'qualification' => 'required|string',
];
if (setting('enable_reCaptcha') == 1) {
$rules['g-recaptcha-response'] = ['required', new Recaptcha];
}
$messages = [
'email.email' => 'Must be a valid email address.',
'g-recaptcha-response.required' => 'Please complete reCAPTCHA validation.',
'g-recaptcha-response' => 'Invalid reCAPTCHA.',
];
$validator = Validator::make($request->all(), $rules, $messages);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
Counselor::create($validator->validated());
return response()->json(['status' => 200, 'message' => "Thank you for reaching out! Your message has been received and we'll get back to you shortly."], 200);
} catch (\Exception $e) {
return response()->json(['status' => 500, 'message' => 'Internal server error', 'error' => $e->getMessage()], 500);
}
}
/**
* Show the specified resource.
*/
public function show($id)
{
return view('ccms::show');
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
return view('ccms::edit');
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
try {
$enquiry = Counselor::whereId($id)->first();
if ($enquiry) {
$enquiry->delete();
}
return response()->json(['status' => 200, 'message' => 'Counselor has been deleted!'], 200);
} catch (\Throwable $th) {
return redirect()->back()->with('error', $th->getMessage());
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Modules\CCMS\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
// use Modules\CCMS\Database\Factories\CounselorFactory;
class Counselor extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*/
protected $guarded = [];
// protected static function newFactory(): CounselorFactory
// {
// // return CounselorFactory::new();
// }
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('counselors', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable();
$table->text('address')->nullable();
$table->string('email')->nullable();
$table->string('contact')->nullable();
$table->string('test_score')->nullable();
$table->string('qualification')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('counselors');
}
};

View File

@@ -0,0 +1,14 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb :title="$title" />
{{ html()->form('POST')->route('testimonial.store')->class(['needs-validation'])->attributes(['enctype' => 'multipart/form-data', 'novalidate'])->open() }}
@include('ccms::testimonial.partials._form')
{{ html()->form()->close() }}
</div>
@endsection

View File

@@ -0,0 +1,10 @@
<div class="hstack flex-wrap gap-3">
<a data-link="{{ route('enquiry.markAsRead', $id) }}" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Mark as {{ $is_read == 1 ? 'unread' : 'read' }}" data-status="{{ $is_read == 1 ? 'read' : 'unread' }}"
class="fs-15 mark-item"><i class="{{ $is_read == 1 ? ' ri-mail-close-line link-secondary' : ' ri-mail-check-line link-success' }}"></i></a>
<a href="javascript:void(0);" data-link="{{ route('enquiry.destroy', $id) }}" class="link-danger fs-15 remove-item" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Delete">
<i class="ri-delete-bin-6-line"></i>
</a>
</div>

View File

@@ -0,0 +1,14 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb :title="$title" />
{{ html()->modelForm($testimonial, 'PUT')->route('testimonial.update', $testimonial->id)->class(['needs-validation'])->attributes(['novalidate'])->open() }}
@include('ccms::testimonial.partials._form')
{{ html()->form()->close() }}
</div>
@endsection

View File

@@ -0,0 +1,37 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb :title="$title" />
<div class="card">
<div class="card-header align-items-center d-flex">
<h5 class="card-title flex-grow-1 mb-0">{{ $title }}</h5>
</div>
<div class="card-body">
@php
$columns = [
[
'title' => 'S.N',
'data' => 'DT_RowIndex',
'name' => 'DT_RowIndex',
'orderable' => false,
'searchable' => false,
'sortable' => false,
],
['title' => 'Name', 'data' => 'name', 'name' => 'name'],
['title' => 'Email', 'data' => 'email', 'name' => 'email'],
['title' => 'Contact', 'data' => 'contact', 'name' => 'contact'],
['title' => 'Test Score', 'data' => 'test_score', 'name' => 'test_score'],
['title' => 'Qualification', 'data' => 'qualification', 'name' => 'qualification'],
[
'title' => 'Action',
'data' => 'action',
'orderable' => false,
'searchable' => false,
],
];
@endphp
<x-data-table-script :route="route('counselor.index')" :reorder="null" :columns="$columns" />
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,71 @@
<div class="row">
<div class="col-lg-8 col-xl-9">
<div class="card">
<div class="card-body">
<div class="row gy-3">
<div class="col-md-6">
{{ html()->label('Name')->class('form-label') }}
{{ html()->span('*')->class('text-danger') }}
{{ html()->text('title')->class('form-control')->placeholder('Enter Name')->required() }}
{{ html()->div('Name is required')->class('invalid-feedback') }}
</div>
<div class="col-md-6">
{{ html()->label('Designation')->class('form-label') }}
{{ html()->text('designation')->class('form-control')->placeholder('Enter Designation') }}
</div>
<div class="col-md-6">
{{ html()->label('Company')->class('form-label') }}
{{ html()->text('company')->class('form-control')->placeholder('Enter Company') }}
</div>
<div class="col-lg-6">
{{ html()->label('Branch')->class('form-label')->for('branch_id') }}
{{ html()->select('branch_id', $branchOptions)->class('form-select choices-select')->placeholder('Select') }}
</div>
<div class="col-12">
{{ html()->label('Comment')->class('form-label')->for('description') }}
{{ html()->span('*')->class('text-danger') }}
{{ html()->textarea('description')->class('form-control')->rows(10) }}
</div>
</div>
</div>
</div>
</div>
<!-- end col -->
<div class="col-lg-4 col-xl-3">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Publish</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
{{ html()->select('status', config('constants.page_status_options'))->class('form-select choices-select ') }}
</div>
</div>
</div>
<!-- end card body -->
<x-form-buttons :editable="$editable" label="Save" href="{{ route('team.index') }}" />
</div>
<div class="card featured-image-section">
<div class="card-header">
<h6 class="card-title mb-0 fs-14">
Featured
</h6>
</div>
<div class="card-body">
<div class="mb-3">
{{ html()->label('Image')->class('form-label')->for('image') }}
<x-image-input :editable="$editable" id="image" name="image" :data="$editable ? $testimonial->getRawOriginal('image') : null"
:multiple="false" />
</div>
</div>
</div>
</div>
<!-- end col -->
</div>

View File

@@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Route;
use Modules\CCMS\Http\Controllers\BlogController;
use Modules\CCMS\Http\Controllers\BranchController;
use Modules\CCMS\Http\Controllers\CategoryController;
use Modules\CCMS\Http\Controllers\CounselorController;
use Modules\CCMS\Http\Controllers\CounterController;
use Modules\CCMS\Http\Controllers\CountryController;
use Modules\CCMS\Http\Controllers\EnquiryController;
@@ -33,7 +34,7 @@ use Modules\CCMS\Http\Controllers\TestimonialController;
|
*/
Route::group(['middleware' => ['web', 'auth', 'permission'],'prefix' => 'admin/'], function () {
Route::group(['middleware' => ['web', 'auth', 'permission'], 'prefix' => 'admin/'], function () {
Route::post('page/reorder', [PageController::class, 'reorder'])->name('page.reorder');
Route::get('page/toggle/{id}', [PageController::class, 'toggle'])->name('page.toggle');
@@ -122,6 +123,6 @@ Route::group(['middleware' => ['web', 'auth', 'permission'],'prefix' => 'admin/'
Route::get('enquiry/mark-as-read/{id}', [EnquiryController::class, 'markAsRead'])->name('enquiry.markAsRead');
Route::resource('enquiry', EnquiryController::class)->names('enquiry')->only(['index', 'store', 'destroy']);
Route::resource('counselor', CounselorController::class)->names('counselor')->only(['index', 'store', 'destroy']);
});

View File

@@ -219,7 +219,45 @@
});
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
const submitBtn = document.getElementById('submit-btn');
const url = form.action;
form.addEventListener('submit', async (e) => {
e.preventDefault();
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting…';
const formData = new FormData(form);
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
.content
},
body: formData
});
const data = await res.json();
if (res.ok) {
form.reset();
toastr.success(data.message || 'Contact Submitted successful!');
} else if (data.errors && data.errors.email) {
data.errors.email.forEach(msg => toastr.error(msg));
} else {
toastr.error('Submittion failed. Please try again.');
}
} catch (err) {
console.error(err);
toastr.error('Something went wrong. Please try again.');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Submit';
}
});
});
</script>
<script>
$(document).ready(function() {
var weekdays = [

View File

@@ -53,12 +53,11 @@
<div class="w-full flex flex-wrap flex-xl-nowrap">
<div class="w-100percent lg:w-full flex animation-element">
<a href="study-usa.php"
<a href="{{ $slider->button_url }}"
class="btn btn-solid btn-hover-txt-marquee btn-hover-txt-marquee-y btn-icon-right lg:text-12 text-18 font-light rounded-20 leading-5 bg-brand module-btn-sm">
<span class="btn-txt " data-text="Find my dream university"
data-split-text="true"
data-split-options='{"type": "chars, words"}'>Find my dream
university</span>
data-split-options='{"type": "chars, words"}'>{{ $slider->button_text }}</span>
<span class="btn-icon mt-3"><i
class="fa-solid fa-arrow-right text-11 bg-white rounded-full text-brand banner-arrow"></i></span></a>
</div>

View File

@@ -31,7 +31,7 @@
consultation </span>with Certified Counsellors</h5>
</div>
<form action="{{ route('enquiry.store') }}" method="POST" id="contact-form">
<form action="{{ route('counselor.store') }}" method="POST" id="counselor-form">
@csrf
<input class="w-full mb-10 rounded-6 py-15 text-14 px-10 border-bottom" type="text"
name="name" id="name" placeholder=" Name">
@@ -43,17 +43,18 @@
<input class="w-60percent mb-10 rounded-6 py-15 text-14 px-10" type="email"
name="email" id="email" placeholder="Your Email">
<input class="w-30percent mb-10 rounded-6 py-15 text-14 px-10" type="number"
inputmode="numeric" name="mobile" id="mobile" placeholder="Contact">
inputmode="numeric" name="contact" id="contact" placeholder="Contact">
</div>
<input class="w-full mb-10 rounded-6 py-15 text-14 px-10" type="text" name="score"
id="score" placeholder="Language Test Score (ilets overall: 7.0 )">
<input class="w-full mb-10 rounded-6 py-15 text-14 px-10" type="text"
name="test_score" id="test_score"
placeholder="Language Test Score (ilets overall: 7.0 )">
<input class="w-full mb-20 rounded-6 py-15 text-14 px-10" type="text"
name="qualification" id="qualification"
placeholder="Recent Education Qualification">
<input class="mb-20" type="checkbox">
<label class="text-14 mb-20" for="">I accept the terms & conditions</label>
<button type="submit" id="submit-btn"
<button type="submit" id="counselor-submit-btn"
class=" w-full py-10 bg-sec text-white rounded-10 text-16 border-0 button-hover">
<i class="fa-solid fa-paper-plane text-white text-16 pr-5"></i>
Send Message</button>