equest for leave
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -352,6 +353,11 @@ class AdminController extends Controller
|
||||
$this->enrichUsersForDashboard($users);
|
||||
$users = $users->sortByDesc('working');
|
||||
$records = $this->loadPendingRequests($users);
|
||||
$leaveRequests = LeaveRequest::query()
|
||||
->where('status', 'pending')
|
||||
->with('user:id,name,user_name')
|
||||
->orderByDesc('created_at')
|
||||
->get();
|
||||
|
||||
return view("admin..admin")->with(['message', [
|
||||
'color' => '#ff8800ff',
|
||||
@@ -359,6 +365,7 @@ class AdminController extends Controller
|
||||
],
|
||||
"users" => $users,
|
||||
'reqs' => $records,
|
||||
'leaveRequests' => $leaveRequests,
|
||||
"userType" => $provider
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Attendance;
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\LatePenaltyService;
|
||||
use App\Support\JalaliHelper;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Morilog\Jalali\Jalalian;
|
||||
|
||||
class LeaveController extends Controller
|
||||
{
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!isset($_COOKIE['user_identity'])) {
|
||||
return redirect('/')->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'ابتدا باید وارد شوید!',
|
||||
]);
|
||||
}
|
||||
|
||||
$user = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
if (!$user) {
|
||||
return redirect('/')->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'کاربر یافت نشد!',
|
||||
]);
|
||||
}
|
||||
|
||||
$type = $request->input('type');
|
||||
if (!in_array($type, ['daily', 'hourly'], true)) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'نوع مرخصی نامعتبر است.',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($type === 'daily') {
|
||||
$dateFrom = $this->parseJalaliDate($request->input('date_from'));
|
||||
$dateTo = $this->parseJalaliDate($request->input('date_to'));
|
||||
|
||||
if (!$dateFrom || !$dateTo) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'بازه تاریخ مرخصی روزانه الزامی است.',
|
||||
]);
|
||||
}
|
||||
if ($dateTo < $dateFrom) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'تاریخ پایان نمیتواند قبل از شروع باشد.',
|
||||
]);
|
||||
}
|
||||
$timeFrom = null;
|
||||
$timeTo = null;
|
||||
} else {
|
||||
$dateFrom = $this->parseJalaliDate($request->input('date'));
|
||||
$dateTo = $dateFrom;
|
||||
$timeFrom = $this->normalizeTime($request->input('time_from'));
|
||||
$timeTo = $this->normalizeTime($request->input('time_to'));
|
||||
|
||||
if (!$dateFrom || !$timeFrom || !$timeTo) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'تاریخ و ساعت شروع/پایان مرخصی ساعتی الزامی است.',
|
||||
]);
|
||||
}
|
||||
if ($timeTo <= $timeFrom) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'ساعت پایان باید بعد از ساعت شروع باشد.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$leave = LeaveRequest::create([
|
||||
'user_id' => $user->id,
|
||||
'user_identity' => $user->user_name,
|
||||
'type' => $type,
|
||||
'date_from' => $dateFrom,
|
||||
'date_to' => $dateTo,
|
||||
'time_from' => $timeFrom,
|
||||
'time_to' => $timeTo,
|
||||
'reason' => $request->input('reason'),
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
$user->req_counts = (int) $user->req_counts + 1;
|
||||
$user->save();
|
||||
|
||||
$this->notifyAdminsNewLeave($leave, $user);
|
||||
|
||||
return redirect('/')->with('message', [
|
||||
'color' => '#00c300ff',
|
||||
'text' => 'درخواست مرخصی ثبت شد و برای تأیید ادمین ارسال گردید.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function decide(Request $request)
|
||||
{
|
||||
if (!isset($_COOKIE['user_identity'])) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'ابتدا باید وارد شوید!',
|
||||
]);
|
||||
}
|
||||
|
||||
$admin = User::where('user_name', $_COOKIE['user_identity'])->first();
|
||||
if (!$admin || !in_array((string) $admin->is_admin, ['1', '2'], true)) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'دسترسی ندارید!',
|
||||
]);
|
||||
}
|
||||
|
||||
$leave = LeaveRequest::find($request->input('leave_id'));
|
||||
if (!$leave || $leave->status !== 'pending') {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff8800ff',
|
||||
'text' => 'درخواست مرخصی یافت نشد.',
|
||||
]);
|
||||
}
|
||||
|
||||
$action = $request->input('update');
|
||||
if (!in_array($action, ['approve', 'reject'], true)) {
|
||||
return back()->with('message', [
|
||||
'color' => '#ff4400ff',
|
||||
'text' => 'عملیات نامعتبر است.',
|
||||
]);
|
||||
}
|
||||
|
||||
$leave->status = $action === 'approve' ? 'approved' : 'rejected';
|
||||
$leave->reviewed_by = $admin->id;
|
||||
$leave->reviewed_at = now();
|
||||
$leave->save();
|
||||
|
||||
$owner = User::where('user_name', $leave->user_identity)->first();
|
||||
if ($owner && (int) $owner->req_counts > 0) {
|
||||
$owner->req_counts -= 1;
|
||||
$owner->save();
|
||||
}
|
||||
|
||||
if ($leave->status === 'approved') {
|
||||
$this->recalculateLateForLeavePeriod($leave);
|
||||
}
|
||||
|
||||
$statusFa = $leave->status === 'approved' ? 'تایید' : 'رد';
|
||||
if ($owner) {
|
||||
tel_notif($leave->user_identity, $statusFa);
|
||||
}
|
||||
|
||||
return back()->with('message', [
|
||||
'color' => '#6aff00ff',
|
||||
'text' => "درخواست مرخصی با موفقیت {$statusFa} شد.",
|
||||
]);
|
||||
}
|
||||
|
||||
private function recalculateLateForLeavePeriod(LeaveRequest $leave): void
|
||||
{
|
||||
$service = app(LatePenaltyService::class);
|
||||
$from = Carbon::parse($leave->date_from)->startOfDay();
|
||||
$to = Carbon::parse($leave->date_to)->endOfDay();
|
||||
|
||||
$attendances = Attendance::query()
|
||||
->where('user_identity', $leave->user_identity)
|
||||
->whereBetween('entered_at', [$from, $to])
|
||||
->orderBy('entered_at')
|
||||
->get();
|
||||
|
||||
foreach ($attendances as $attendance) {
|
||||
$service->syncAttendance($attendance);
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyAdminsNewLeave(LeaveRequest $leave, User $user): void
|
||||
{
|
||||
$typeFa = $leave->type === 'daily' ? 'روزانه' : 'ساعتی';
|
||||
$from = JalaliHelper::format($leave->date_from, 'Y/m/d');
|
||||
$to = JalaliHelper::format($leave->date_to, 'Y/m/d');
|
||||
|
||||
$message = "درخواست مرخصی {$typeFa}\n";
|
||||
$message .= "کارمند: {$user->name}\n";
|
||||
if ($leave->type === 'daily') {
|
||||
$message .= "از {$from} تا {$to}\n";
|
||||
} else {
|
||||
$timeFrom = substr((string) $leave->time_from, 0, 5);
|
||||
$timeTo = substr((string) $leave->time_to, 0, 5);
|
||||
$message .= "تاریخ: {$from}\nساعت: {$timeFrom} تا {$timeTo}\n";
|
||||
}
|
||||
if ($leave->reason) {
|
||||
$message .= "دلیل: {$leave->reason}";
|
||||
}
|
||||
|
||||
$this->send_admins($message);
|
||||
}
|
||||
|
||||
private function parseJalaliDate(?string $value): ?string
|
||||
{
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = \Morilog\Jalali\CalendarUtils::convertNumbers(trim($value), true);
|
||||
$value = str_replace('/', '-', $value);
|
||||
|
||||
try {
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
||||
// اگر سال شمسی باشد (مثلاً 1404) با Jalalian تبدیل میشود
|
||||
$year = (int) substr($value, 0, 4);
|
||||
if ($year >= 1300 && $year <= 1500) {
|
||||
return Jalalian::fromFormat('Y-m-d', $value)->toCarbon()->format('Y-m-d');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return Carbon::parse($value)->format('Y-m-d');
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeTime(?string $value): ?string
|
||||
{
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
$value = trim(\Morilog\Jalali\CalendarUtils::convertNumbers($value, true));
|
||||
if (preg_match('/^(\d{1,2}):(\d{2})/', $value, $m)) {
|
||||
return sprintf('%02d:%02d:00', (int) $m[1], (int) $m[2]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LeaveRequest extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'user_identity',
|
||||
'type',
|
||||
'date_from',
|
||||
'date_to',
|
||||
'time_from',
|
||||
'time_to',
|
||||
'reason',
|
||||
'status',
|
||||
'reviewed_by',
|
||||
'reviewed_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'date_from' => 'date',
|
||||
'date_to' => 'date',
|
||||
'reviewed_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function reviewer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reviewed_by');
|
||||
}
|
||||
|
||||
public function isDaily(): bool
|
||||
{
|
||||
return $this->type === 'daily';
|
||||
}
|
||||
|
||||
public function isHourly(): bool
|
||||
{
|
||||
return $this->type === 'hourly';
|
||||
}
|
||||
|
||||
public function isApproved(): bool
|
||||
{
|
||||
return $this->status === 'approved';
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Services;
|
||||
|
||||
use App\Models\Attendance;
|
||||
use App\Models\LatePeriodFine;
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -17,7 +18,7 @@ class LatePenaltyService
|
||||
public const WEEKLY_ALLOWANCE_MINUTES = 15;
|
||||
public const FINE_PER_MINUTE = 2000;
|
||||
|
||||
public function calculateFromEnteredAt(?string $enteredAt): array
|
||||
public function calculateFromEnteredAt(?string $enteredAt, ?string $userIdentity = null): array
|
||||
{
|
||||
if (!$enteredAt) {
|
||||
return [
|
||||
@@ -32,6 +33,21 @@ class LatePenaltyService
|
||||
$entered = Carbon::parse($enteredAt)->startOfMinute();
|
||||
$threshold = $entered->copy()->setTimeFromTimeString(self::WORK_START)->startOfMinute();
|
||||
|
||||
if ($userIdentity) {
|
||||
$leaveThreshold = $this->approvedLeaveWorkThreshold($userIdentity, $entered);
|
||||
if ($leaveThreshold === false) {
|
||||
return [
|
||||
'late_minutes' => 0,
|
||||
'grace_late_minutes' => 0,
|
||||
'daily_fine_minutes' => 0,
|
||||
'daily_fine_amount' => 0,
|
||||
];
|
||||
}
|
||||
if ($leaveThreshold instanceof Carbon) {
|
||||
$threshold = $leaveThreshold->copy()->startOfMinute();
|
||||
}
|
||||
}
|
||||
|
||||
if ($entered->lte($threshold)) {
|
||||
return [
|
||||
'late_minutes' => 0,
|
||||
@@ -54,6 +70,49 @@ class LatePenaltyService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* مرخصی تأییدشده: false = معاف کامل، Carbon = شروع مؤثر کار، null = بدون تغییر.
|
||||
*/
|
||||
public function approvedLeaveWorkThreshold(string $userIdentity, Carbon $entered): Carbon|false|null
|
||||
{
|
||||
$day = $entered->toDateString();
|
||||
$workStart = $entered->copy()->setTimeFromTimeString(self::WORK_START)->startOfMinute();
|
||||
|
||||
$leaves = LeaveRequest::query()
|
||||
->where('user_identity', $userIdentity)
|
||||
->where('status', 'approved')
|
||||
->whereDate('date_from', '<=', $day)
|
||||
->whereDate('date_to', '>=', $day)
|
||||
->get();
|
||||
|
||||
$latestMorningLeaveEnd = null;
|
||||
|
||||
foreach ($leaves as $leave) {
|
||||
if ($leave->type === 'daily') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($leave->type !== 'hourly' || !$leave->time_from || !$leave->time_to) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$leaveStart = Carbon::parse($day . ' ' . $leave->time_from)->startOfMinute();
|
||||
$leaveEnd = Carbon::parse($day . ' ' . $leave->time_to)->startOfMinute();
|
||||
|
||||
// مرخصی صبح که شروع کار را پوشش میدهد
|
||||
if ($leaveStart->lte($workStart) && $leaveEnd->gt($workStart)) {
|
||||
if ($entered->lte($leaveEnd)) {
|
||||
return false;
|
||||
}
|
||||
if ($latestMorningLeaveEnd === null || $leaveEnd->gt($latestMorningLeaveEnd)) {
|
||||
$latestMorningLeaveEnd = $leaveEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $latestMorningLeaveEnd;
|
||||
}
|
||||
|
||||
/** دقایق تأخیر داخل بازه ۸:۳۰–۸:۴۵ برای یک رکورد */
|
||||
public function graceLateMinutes(Attendance $att): int
|
||||
{
|
||||
@@ -94,7 +153,7 @@ class LatePenaltyService
|
||||
return $attendance;
|
||||
}
|
||||
|
||||
$calc = $this->calculateFromEnteredAt($attendance->entered_at);
|
||||
$calc = $this->calculateFromEnteredAt($attendance->entered_at, $attendance->user_identity);
|
||||
$attendance->late_minutes = $calc['late_minutes'];
|
||||
$attendance->daily_fine_minutes = $calc['daily_fine_minutes'];
|
||||
$attendance->daily_fine_amount = $calc['daily_fine_amount'];
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('leave_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id')->nullable();
|
||||
$table->string('user_identity');
|
||||
$table->string('type'); // daily | hourly
|
||||
$table->date('date_from');
|
||||
$table->date('date_to');
|
||||
$table->time('time_from')->nullable();
|
||||
$table->time('time_to')->nullable();
|
||||
$table->text('reason')->nullable();
|
||||
$table->string('status')->default('pending'); // pending | approved | rejected
|
||||
$table->unsignedBigInteger('reviewed_by')->nullable();
|
||||
$table->timestamp('reviewed_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['user_identity', 'status']);
|
||||
$table->index(['date_from', 'date_to']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('leave_requests');
|
||||
}
|
||||
};
|
||||
@@ -11,6 +11,62 @@ $usersChunked = $users->chunk(3);
|
||||
@section('content')
|
||||
<div class="container w-full flex flex-col items-center">
|
||||
|
||||
@if(isset($leaveRequests) && $leaveRequests->isNotEmpty())
|
||||
<div class="ui-requests-card flex flex-col items-center w-full max-w-[350px] mb-4">
|
||||
<p>درخواستهای مرخصی</p>
|
||||
<div class="ui-table-wrap w-full pt-2">
|
||||
<table class="w-full table-auto text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-2 py-2">کارمند</th>
|
||||
<th class="px-2 py-2">نوع</th>
|
||||
<th class="px-2 py-2">بازه</th>
|
||||
<th class="px-2 py-2">وضعیت</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($leaveRequests as $leave)
|
||||
@php
|
||||
$ownerName = $leave->user->name ?? $leave->user_identity;
|
||||
$fromJ = \App\Support\JalaliHelper::format($leave->date_from, 'Y/m/d');
|
||||
$toJ = \App\Support\JalaliHelper::format($leave->date_to, 'Y/m/d');
|
||||
if ($leave->type === 'daily') {
|
||||
$range = $fromJ === $toJ ? $fromJ : "{$fromJ} تا {$toJ}";
|
||||
} else {
|
||||
$tf = substr((string) $leave->time_from, 0, 5);
|
||||
$tt = substr((string) $leave->time_to, 0, 5);
|
||||
$range = "{$fromJ}<br>{$tf}–{$tt}";
|
||||
}
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="px-2 py-2 border">{{ $ownerName }}</td>
|
||||
<td class="px-2 py-2 border">{{ $leave->type === 'daily' ? 'روزانه' : 'ساعتی' }}</td>
|
||||
<td class="px-2 py-2 border">{!! $range !!}</td>
|
||||
<td class="px-2 py-2 border">
|
||||
@if($leave->reason)
|
||||
<p class="text-xs mb-1 opacity-80">{{ $leave->reason }}</p>
|
||||
@endif
|
||||
<form method="post" action="{{ route('admin.leave.decide') }}" class="inline">
|
||||
@csrf
|
||||
<input type="hidden" name="leave_id" value="{{ $leave->id }}">
|
||||
<input type="hidden" name="update" value="approve">
|
||||
<button type="submit" class="ui-btn-approve">تایید</button>
|
||||
</form>
|
||||
<form method="post" action="{{ route('admin.leave.decide') }}" class="inline">
|
||||
@csrf
|
||||
<input type="hidden" name="leave_id" value="{{ $leave->id }}">
|
||||
<input type="hidden" name="update" value="reject">
|
||||
<button type="submit" class="ui-btn-reject">رد</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!collect($reqs)->pluck('attendances')->flatten()->isEmpty())
|
||||
<div class="ui-requests-card flex flex-col items-center w-full max-w-[350px]">
|
||||
<p>درخواست ها</p>
|
||||
|
||||
@@ -82,19 +82,25 @@ if (!empty($user->tasks)) {
|
||||
|
||||
<div id="output" class="text-red-500 text-sm mt-2 text-center"></div>
|
||||
|
||||
<div class="flex justify-flex max-w-[240px]" style="align-items: center;">
|
||||
<div class="flex justify-flex max-w-[240px] flex-wrap gap-2" style="align-items: center;">
|
||||
<button type="button"
|
||||
class="w-1/2 p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600 cursor-pointer"
|
||||
class="w-[calc(50%-4px)] p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600 cursor-pointer"
|
||||
onclick="openModal('ثبت روز کاری جدید', 'new')">
|
||||
ثبت روز کاری
|
||||
</button>
|
||||
|
||||
<button type="button" id="breakAttendance"
|
||||
class="w-1/2 p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 cursor-pointer mx-auto"
|
||||
class="w-[calc(50%-4px)] p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 cursor-pointer"
|
||||
href="">
|
||||
استراحت
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="w-full p-2 text-sm text-white text-center rounded-xl bg-teal-500 hover:bg-teal-600 cursor-pointer"
|
||||
onclick="openLeaveModal()">
|
||||
درخواست مرخصی
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" id="tasksBtn"
|
||||
class="text-white rounded-xl py-2 px-4 max-w-60 self-center bg-cyan-500 hover:bg-cyan-700 cursor-pointer w-full"
|
||||
@@ -120,11 +126,19 @@ if (!empty($user->tasks)) {
|
||||
{{$user->name}}
|
||||
</label>
|
||||
|
||||
<button type="button"
|
||||
class="w-1/2 p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600 cursor-pointer"
|
||||
onclick="openModal('ثبت روز کاری جدید', 'new')">
|
||||
ثبت روز کاری
|
||||
</button>
|
||||
<div class="flex w-full max-w-[240px] flex-wrap gap-2 justify-center">
|
||||
<button type="button"
|
||||
class="w-[calc(50%-4px)] p-2 text-sm text-white text-center rounded-xl bg-cyan-500 hover:bg-cyan-600 cursor-pointer"
|
||||
onclick="openModal('ثبت روز کاری جدید', 'new')">
|
||||
ثبت روز کاری
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="w-[calc(50%-4px)] p-2 text-sm text-white text-center rounded-xl bg-teal-500 hover:bg-teal-600 cursor-pointer"
|
||||
onclick="openLeaveModal()">
|
||||
درخواست مرخصی
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -403,6 +417,70 @@ if (!empty($user->tasks)) {
|
||||
</div>
|
||||
|
||||
|
||||
{{-- Leave Request Modal --}}
|
||||
<div id="leave-modal-container"
|
||||
class="ui-modal-backdrop centered hidden">
|
||||
<div class="ui-modal-panel max-w-sm w-full">
|
||||
<h2 class="ui-title">درخواست مرخصی</h2>
|
||||
<p class="ui-subtitle">نوع و بازه مرخصی را مشخص کنید</p>
|
||||
|
||||
<form method="post" action="{{ route('leave.store') }}" class="flex flex-col gap-3 items-stretch px-2"
|
||||
onsubmit="return confirm('درخواست مرخصی ارسال شود؟')">
|
||||
@csrf
|
||||
|
||||
<div class="flex gap-2 justify-center">
|
||||
<label class="flex items-center gap-1 text-sm cursor-pointer">
|
||||
<input type="radio" name="type" value="daily" checked onchange="toggleLeaveType()">
|
||||
روزانه
|
||||
</label>
|
||||
<label class="flex items-center gap-1 text-sm cursor-pointer">
|
||||
<input type="radio" name="type" value="hourly" onchange="toggleLeaveType()">
|
||||
ساعتی
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="leave-daily-fields" class="flex flex-col gap-2">
|
||||
<label class="text-sm">از تاریخ</label>
|
||||
<input type="text" name="date_from" id="leave_date_from"
|
||||
class="leave-datepicker form-control border p-2 rounded" placeholder="تاریخ شروع" readonly>
|
||||
<label class="text-sm">تا تاریخ</label>
|
||||
<input type="text" name="date_to" id="leave_date_to"
|
||||
class="leave-datepicker form-control border p-2 rounded" placeholder="تاریخ پایان" readonly>
|
||||
</div>
|
||||
|
||||
<div id="leave-hourly-fields" class="flex flex-col gap-2" style="display:none;">
|
||||
<label class="text-sm">تاریخ</label>
|
||||
<input type="text" name="date" id="leave_date"
|
||||
class="leave-datepicker form-control border p-2 rounded" placeholder="تاریخ" readonly>
|
||||
<label class="text-sm">از ساعت</label>
|
||||
<div class="input-group clockpicker border-2 border-gray-300 rounded-lg">
|
||||
<input type="text" name="time_from" id="leave_time_from"
|
||||
class="form-control cursor-default p-2 w-full" placeholder="08:30" readonly>
|
||||
</div>
|
||||
<label class="text-sm">تا ساعت</label>
|
||||
<div class="input-group clockpicker border-2 border-gray-300 rounded-lg">
|
||||
<input type="text" name="time_to" id="leave_time_to"
|
||||
class="form-control cursor-default p-2 w-full" placeholder="12:00" readonly>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="text-sm">دلیل (اختیاری)</label>
|
||||
<textarea name="reason" rows="2" class="border p-2 rounded text-sm" placeholder="توضیح کوتاه..."></textarea>
|
||||
|
||||
<div class="flex gap-2 justify-center mt-2">
|
||||
<button type="submit" class="bg-teal-500 hover:bg-teal-600 text-white rounded p-2 w-[70%]">
|
||||
ارسال درخواست
|
||||
</button>
|
||||
<button type="button" class="bg-gray-400 hover:bg-gray-500 text-white rounded p-2 w-[30%]"
|
||||
onclick="closeLeaveModal()">
|
||||
بستن
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- START TASK MODAL --}}
|
||||
<div id="taskModal"
|
||||
class="ui-modal-backdrop hidden"
|
||||
@@ -1065,6 +1143,69 @@ if (!empty($user->tasks)) {
|
||||
document.getElementById('modal-container').classList.add('hidden');
|
||||
}
|
||||
|
||||
function openLeaveModal() {
|
||||
document.getElementById('leave-modal-container').classList.remove('hidden');
|
||||
toggleLeaveType();
|
||||
if (!window._leavePickersReady) {
|
||||
$('.leave-datepicker').persianDatepicker({
|
||||
format: "YYYY/MM/DD",
|
||||
calendar: {
|
||||
persian: {
|
||||
locale: 'fa',
|
||||
leapYearMode: "astronomical"
|
||||
}
|
||||
},
|
||||
observer: true,
|
||||
initialValue: false,
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: "امروز"}}
|
||||
},
|
||||
});
|
||||
$('#leave-modal-container .clockpicker').clockpicker({
|
||||
autoclose: false,
|
||||
placement: 'center',
|
||||
donetext: 'ثبت',
|
||||
vibrate: true,
|
||||
});
|
||||
window._leavePickersReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeLeaveModal() {
|
||||
document.getElementById('leave-modal-container').classList.add('hidden');
|
||||
}
|
||||
|
||||
function toggleLeaveType() {
|
||||
const type = document.querySelector('input[name="type"]:checked')?.value || 'daily';
|
||||
const daily = document.getElementById('leave-daily-fields');
|
||||
const hourly = document.getElementById('leave-hourly-fields');
|
||||
if (type === 'hourly') {
|
||||
daily.style.display = 'none';
|
||||
hourly.style.display = 'flex';
|
||||
document.getElementById('leave_date_from').required = false;
|
||||
document.getElementById('leave_date_to').required = false;
|
||||
document.getElementById('leave_date').required = true;
|
||||
document.getElementById('leave_time_from').required = true;
|
||||
document.getElementById('leave_time_to').required = true;
|
||||
} else {
|
||||
daily.style.display = 'flex';
|
||||
hourly.style.display = 'none';
|
||||
document.getElementById('leave_date_from').required = true;
|
||||
document.getElementById('leave_date_to').required = true;
|
||||
document.getElementById('leave_date').required = false;
|
||||
document.getElementById('leave_time_from').required = false;
|
||||
document.getElementById('leave_time_to').required = false;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('leave-modal-container')?.addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('leave-modal-container')) {
|
||||
closeLeaveModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('modal-container').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('modal-container')) {
|
||||
closeModal();
|
||||
|
||||
@@ -19,6 +19,7 @@ use GuzzleHttp\Client;
|
||||
use App\Models\Tasks;
|
||||
use App\Models\Pivot_Tasks;
|
||||
use App\Http\Controllers\AttendanceController;
|
||||
use App\Http\Controllers\LeaveController;
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +35,8 @@ Route::post('/delete', [AdminController::class, 'delete'])->name('admin.delete')
|
||||
|
||||
Route::post('/update', [AdminController::class, 'update'])->name('admin.update');
|
||||
|
||||
Route::post('/leave/decide', [LeaveController::class, 'decide'])->name('admin.leave.decide');
|
||||
|
||||
|
||||
|
||||
Route::get('/job', function () {
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Models\Pivot_Tasks;
|
||||
use App\Http\Controllers\AttendanceController;
|
||||
use App\Http\Controllers\TaskController;
|
||||
use App\Http\Controllers\FingerprintController;
|
||||
use App\Http\Controllers\LeaveController;
|
||||
|
||||
|
||||
Route::prefix('admin')->middleware('admin')->group(function () {
|
||||
@@ -58,6 +59,7 @@ Route::get('/attendance/profile', [AttendanceController::class, 'profile'])->nam
|
||||
|
||||
Route::post('/attendance/profile', [AttendanceController::class, 'updateProfile'])->name('edit.profile');
|
||||
|
||||
Route::post('/leave', [LeaveController::class, 'store'])->name('leave.store');
|
||||
|
||||
//Task routes
|
||||
Route::get('/tasks', [TaskController::class, 'Tasks']);
|
||||
|
||||
Reference in New Issue
Block a user