feat: Add Vacancy management functionality with controller, model, migration, and form

This commit is contained in:
2025-08-22 17:03:00 +05:45
parent 52732b0f09
commit 6d71fe4b4a
7 changed files with 258 additions and 12 deletions

View File

@@ -0,0 +1,147 @@
<?php
namespace Modules\CCMS\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Rules\Recaptcha;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Validator;
use Modules\CCMS\Models\Vacancy;
use Yajra\DataTables\Facades\DataTables;
class VacancyController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
if (request()->ajax()) {
$model = Vacancy::query()->latest();
return DataTables::eloquent($model)
->addIndexColumn()
// ->setRowClass(function (Vacancy $vacancy) {
// return $vacancy->is_read ? 'text-muted' : 'text-dark';
// })
// ->editColumn('class', function (Vacancy $vacancy) {
// return $vacancy->class ?? '-';
// })
// ->editColumn('subject', function (Vacancy $vacancy) {
// return $vacancy->subject ?? '-';
// })
// ->editColumn('message', function (Vacancy $vacancy) {
// return $vacancy->message ?? '-';
// })
->addColumn('action', 'ccms::vacancy.datatable.action')
->rawColumns(['action'])
->toJson();
}
return view('ccms::vacancy.index', [
'title' => 'Vacancy List',
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
try {
// $rules = [
// ];
// 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());
// if ($validator->fails()) {
// return response()->json(['errors' => $validator->errors()], 422);
// }
$modelClass = "Modules\\CCMS\\Models\\Career";
$model = $modelClass::findOrFail($request->career_id);
foreach ($request->document as $file) {
$model->addToDocumentCollection(collectionName: 'uploads/document', file: $file, documentName: $request->first_name, referenceDocumentId: null);
}
Vacancy::create($request->all());
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)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
try {
$vacancy = Vacancy::whereId($id)->first();
if ($vacancy) {
$vacancy->delete();
}
return response()->json(['status' => 200, 'message' => 'Vacancy has been deleted!'], 200);
} catch (\Throwable $th) {
return redirect()->back()->with('error', $th->getMessage());
}
}
public function markAsRead($id)
{
try {
$vacancy = Vacancy::whereId($id)->first();
if ($vacancy) {
$vacancy->update(['is_read' => 1]);
}
return response()->json(['status' => 200, 'message' => 'Vacancy has been marked as read!'], 200);
} catch (\Throwable $th) {
return redirect()->back()->with('error', $th->getMessage());
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\CCMS\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
// use Modules\CCMS\Database\Factories\VacancyFactory;
class vacancy extends Model
{
use HasFactory;
protected $fillable = [
'first_name',
'last_name',
'email',
'phone',
'qualification',
'description',
];
}

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('vacancies', function (Blueprint $table) {
$table->id();
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->string('email')->nullable();
$table->string('phone')->nullable();
$table->string('qualification')->nullable();
$table->text('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('vacancies');
}
};

View File

@@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Route;
use Modules\CCMS\Http\Controllers\EnquiryController;
use Modules\CCMS\Http\Controllers\FranchiseController;
use Modules\CCMS\Http\Controllers\NewsletterController;
use Modules\CCMS\Http\Controllers\VacancyController;
use Modules\CCMS\Models\Franchise;
use Modules\CourseFinder\Http\Controllers\CoopController;
use Modules\CourseFinder\Http\Controllers\ProgramController;
@@ -24,6 +25,7 @@ Route::get('getCoursesList', [ProgramController::class, 'getCoursesList'])->name
Route::post('enquiry', [EnquiryController::class, 'store'])->name('enquiry.store');
Route::post('franchise', [FranchiseController::class, 'store'])->name('franchise.store');
Route::post('newsletter', [NewsletterController::class, 'store'])->name('newsletter.store');
Route::post('vacancy', [VacancyController::class, 'store'])->name('vacancy.store');
Route::get('career/{id}', [WebsiteController::class, 'careerSingle'])->name('career.single');

View File

@@ -11,6 +11,7 @@ trait AddToDocumentCollection
{
public function addToDocumentCollection(string $collectionName = 'uploads', string $file, ?string $documentName = null, ?int $referenceDocumentId = null)
{
dd($documentName);
if (!Storage::disk('public')->exists($collectionName)) {
Storage::disk('public')->makeDirectory($collectionName);
}

View File

@@ -339,6 +339,46 @@
});
</script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('vacancy-form');
const submitBtn = document.getElementById('vacancy-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();
window.location.href =
"{{ route('thankyou') }}"; // ✅ redirect instead of toastr
} else if (data.errors && data.errors.email) {
data.errors.email.forEach(msg => toastr.error(msg));
} else {
toastr.error('Submission 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

@@ -89,35 +89,37 @@
<div class="col col-12 col-md-5">
<div class=" form ">
<h2 class="text-26 mb-30 text-brand text-center">Quick Apply</h2>
<form action="">
<form action="{{ route('vacancy.store') }}" method="post" id="vacancy-form">
@csrf
<input type="hidden" name="career_id" value="{{ $career->id }}">
<div class="flex gap-20">
<input class="w-full mb-30 rounded-6 py-15 text-14 px-10" type="text"
name="" id="" placeholder="First Name">
name="first_name" id="" placeholder="First Name">
<input class="w-full mb-30 rounded-6 py-15 text-14 px-10" type="text"
name="" id="" placeholder="Last Name">
name="last_name" id="" placeholder="Last Name">
</div>
<div class="flex gap-20">
<input class="w-full mb-30 rounded-6 py-15 text-14 px-10" type="email"
name="" id="" placeholder="Email">
name="email" id="" placeholder="Email">
<input class="w-full mb-30 rounded-6 py-15 text-14 px-10" type="tel"
name="" id="" placeholder="Phone Number">
name="phone" id="" placeholder="Phone Number">
</div>
<input class="w-full mb-30 rounded-6 py-15 text-14 px-10" type="text" name=""
id="" placeholder="Your Qualification">
<input class="w-full mb-30 rounded-6 py-15 text-14 px-10" type="text"
name="qualification" id="" placeholder="Your Qualification">
<textarea class="w-full mb-10 rounded-6 py-15 text-14 px-10" name="" id=""
<textarea class="w-full mb-10 rounded-6 py-15 text-14 px-10" name="description" id=""
placeholder="Short description about you"></textarea>
<label class="text-16" for="">Please Upload Your CV</label>
<input class="mb-20" type="file" name="" id="">
<button
<input class="mb-20" type="file" name="document" id="">
<button type="submit" id="vacancy-submit-btn"
class="button-hover px-30 py-10 bg-sec text-white rounded-30 text-14 border-0 mt-20 ">Submit</button>
</form>