Initial commit - lms-v2 + CLAUDE.md
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1 @@
|
||||
//
|
||||
@@ -0,0 +1,10 @@
|
||||
@props(['on'])
|
||||
|
||||
<div x-data="{ shown: false, timeout: null }"
|
||||
x-init="@this.on('{{ $on }}', () => { clearTimeout(timeout); shown = true; timeout = setTimeout(() => { shown = false }, 2000); })"
|
||||
x-show.transition.out.opacity.duration.1500ms="shown"
|
||||
x-transition:leave.opacity.duration.1500ms
|
||||
style="display: none;"
|
||||
{{ $attributes->merge(['class' => 'text-sm text-gray-600']) }}>
|
||||
{{ $slot->isEmpty() ? __('Saved.') : $slot }}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,7 @@
|
||||
@props(['status'])
|
||||
|
||||
@if ($status)
|
||||
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
|
||||
{{ $status }}
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1 @@
|
||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||
@@ -0,0 +1,35 @@
|
||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
|
||||
|
||||
@php
|
||||
$alignmentClasses = match ($align) {
|
||||
'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
|
||||
'top' => 'origin-top',
|
||||
default => 'ltr:origin-top-right rtl:origin-top-left end-0',
|
||||
};
|
||||
|
||||
$width = match ($width) {
|
||||
'48' => 'w-48',
|
||||
default => $width,
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||
<div @click="open = ! open">
|
||||
{{ $trigger }}
|
||||
</div>
|
||||
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||
style="display: none;"
|
||||
@click="open = false">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
|
||||
{{ $content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
@props(['messages'])
|
||||
|
||||
@if ($messages)
|
||||
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
|
||||
@foreach ((array) $messages as $message)
|
||||
<li>{{ $message }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@@ -0,0 +1,5 @@
|
||||
@props(['value'])
|
||||
|
||||
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
|
||||
{{ $value ?? $slot }}
|
||||
</label>
|
||||
@@ -0,0 +1,78 @@
|
||||
@props([
|
||||
'name',
|
||||
'show' => false,
|
||||
'maxWidth' => '2xl'
|
||||
])
|
||||
|
||||
@php
|
||||
$maxWidth = [
|
||||
'sm' => 'sm:max-w-sm',
|
||||
'md' => 'sm:max-w-md',
|
||||
'lg' => 'sm:max-w-lg',
|
||||
'xl' => 'sm:max-w-xl',
|
||||
'2xl' => 'sm:max-w-2xl',
|
||||
][$maxWidth];
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
show: @js($show),
|
||||
focusables() {
|
||||
// All focusable element types...
|
||||
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||
return [...$el.querySelectorAll(selector)]
|
||||
// All non-disabled elements...
|
||||
.filter(el => ! el.hasAttribute('disabled'))
|
||||
},
|
||||
firstFocusable() { return this.focusables()[0] },
|
||||
lastFocusable() { return this.focusables().slice(-1)[0] },
|
||||
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
||||
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
||||
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
||||
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||
}"
|
||||
x-init="$watch('show', value => {
|
||||
if (value) {
|
||||
document.body.classList.add('overflow-y-hidden');
|
||||
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
|
||||
} else {
|
||||
document.body.classList.remove('overflow-y-hidden');
|
||||
}
|
||||
})"
|
||||
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
||||
x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null"
|
||||
x-on:close.stop="show = false"
|
||||
x-on:keydown.escape.window="show = false"
|
||||
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||
x-show="show"
|
||||
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
||||
style="display: {{ $show ? 'block' : 'none' }};"
|
||||
>
|
||||
<div
|
||||
x-show="show"
|
||||
class="fixed inset-0 transform transition-all"
|
||||
x-on:click="show = false"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="show"
|
||||
class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,3 @@
|
||||
@props(['disabled' => false])
|
||||
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
|
||||
@@ -0,0 +1,17 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900">
|
||||
{{ __("You're logged in!") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>@yield('title', 'Tunggal Pharma LMS')</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="font-sans antialiased text-slate-900 bg-slate-100">
|
||||
|
||||
<div class="flex h-screen overflow-hidden bg-slate-100">
|
||||
|
||||
@include('partials.sidebar')
|
||||
|
||||
<div class="flex-1 flex flex-col overflow-hidden relative">
|
||||
|
||||
@include('partials.navbar')
|
||||
|
||||
<main class="flex-1 overflow-y-auto bg-slate-50 p-4 sm:p-6 lg:p-8">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
@include('partials.footer')
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>Tunggal Pharma LMS</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700,800&display=swap" rel="stylesheet" />
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="font-sans text-slate-900 antialiased overflow-x-hidden">
|
||||
|
||||
<main class="w-full min-h-screen">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Actions\Logout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
/**
|
||||
* Log the current user out of the application.
|
||||
*/
|
||||
public function logout(Logout $logout): void
|
||||
{
|
||||
$logout();
|
||||
|
||||
$this->redirect('/', navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
|
||||
<!-- Primary Navigation Menu -->
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<!-- Logo -->
|
||||
<div class="shrink-0 flex items-center">
|
||||
<a href="{{ route('dashboard') }}" wire:navigate>
|
||||
<x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
|
||||
<x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
</x-nav-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Dropdown -->
|
||||
<div class="hidden sm:flex sm:items-center sm:ms-6">
|
||||
<x-dropdown align="right" width="48">
|
||||
<x-slot name="trigger">
|
||||
<button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
|
||||
<div x-data="{{ json_encode(['name' => auth()->user()->name]) }}" x-text="name" x-on:profile-updated.window="name = $event.detail.name"></div>
|
||||
|
||||
<div class="ms-1">
|
||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<x-dropdown-link :href="route('profile')" wire:navigate>
|
||||
{{ __('Profile') }}
|
||||
</x-dropdown-link>
|
||||
|
||||
<!-- Authentication -->
|
||||
<button wire:click="logout" class="w-full text-start">
|
||||
<x-dropdown-link>
|
||||
{{ __('Log Out') }}
|
||||
</x-dropdown-link>
|
||||
</button>
|
||||
</x-slot>
|
||||
</x-dropdown>
|
||||
</div>
|
||||
|
||||
<!-- Hamburger -->
|
||||
<div class="-me-2 flex items-center sm:hidden">
|
||||
<button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
|
||||
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
|
||||
<path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
<path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Responsive Navigation Menu -->
|
||||
<div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
|
||||
<div class="pt-2 pb-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
</x-responsive-nav-link>
|
||||
</div>
|
||||
|
||||
<!-- Responsive Settings Options -->
|
||||
<div class="pt-4 pb-1 border-t border-gray-200">
|
||||
<div class="px-4">
|
||||
<div class="font-medium text-base text-gray-800" x-data="{{ json_encode(['name' => auth()->user()->name]) }}" x-text="name" x-on:profile-updated.window="name = $event.detail.name"></div>
|
||||
<div class="font-medium text-sm text-gray-500">{{ auth()->user()->email }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 space-y-1">
|
||||
<x-responsive-nav-link :href="route('profile')" wire:navigate>
|
||||
{{ __('Profile') }}
|
||||
</x-responsive-nav-link>
|
||||
|
||||
<!-- Authentication -->
|
||||
<button wire:click="logout" class="w-full text-start">
|
||||
<x-responsive-nav-link>
|
||||
{{ __('Log Out') }}
|
||||
</x-responsive-nav-link>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-bold text-slate-800 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path></svg>
|
||||
Status Kepatuhan SOP & CPOB
|
||||
</h3>
|
||||
<span class="text-xs font-semibold px-2 py-1 bg-indigo-50 text-indigo-600 rounded-md">Live Realtime</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="flex justify-between text-sm mb-1">
|
||||
<span class="font-medium text-slate-600">Tingkat Kepatuhan Perusahaan</span>
|
||||
<span class="font-bold text-indigo-700">{{ $complianceRate }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-slate-100 rounded-full h-2.5">
|
||||
<div class="bg-indigo-600 h-2.5 rounded-full transition-all duration-500" style="width: {{ $complianceRate }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mt-6">
|
||||
<div class="bg-slate-50 p-3 rounded-lg border border-slate-100">
|
||||
<p class="text-xs text-slate-500 mb-1">Karyawan Tersertifikasi</p>
|
||||
<p class="text-xl font-bold text-emerald-600">{{ $karyawanLulus }} <span class="text-xs font-normal text-slate-400">Orang</span></p>
|
||||
</div>
|
||||
<div class="bg-slate-50 p-3 rounded-lg border border-slate-100">
|
||||
<p class="text-xs text-slate-500 mb-1">Total Karyawan Aktif</p>
|
||||
<p class="text-xl font-bold text-slate-700">{{ $totalKaryawan }} <span class="text-xs font-normal text-slate-400">Orang</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('layouts.guest')] class extends Component
|
||||
{
|
||||
public string $password = '';
|
||||
|
||||
/**
|
||||
* Confirm the current user's password.
|
||||
*/
|
||||
public function confirmPassword(): void
|
||||
{
|
||||
$this->validate([
|
||||
'password' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => Auth::user()->email,
|
||||
'password' => $this->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
session(['auth.password_confirmed_at' => time()]);
|
||||
|
||||
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||
</div>
|
||||
|
||||
<form wire:submit="confirmPassword">
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input wire:model="password"
|
||||
id="password"
|
||||
class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="current-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Confirm') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,84 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-6 text-center">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">Pembaruan Keamanan</h2>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
Demi keamanan data pelatihan, Anda diwajibkan untuk mengganti password default (12345678) dengan password baru yang lebih aman sebelum melanjutkan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="mb-4 p-4 rounded-md bg-red-50 border border-red-200">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
|
||||
Pembaruan Gagal
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-red-700 dark:text-red-300">
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('password.force-update') }}" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Password Baru
|
||||
</label>
|
||||
<div class="mt-1 relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input id="password" name="password" type="password" required autocomplete="new-password"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-10 sm:text-sm border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
||||
placeholder="Minimal 8 karakter">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password_confirmation" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Ulangi Password Baru
|
||||
</label>
|
||||
<div class="mt-1 relative rounded-md shadow-sm">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input id="password_confirmation" name="password_confirmation" type="password" required autocomplete="new-password"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-10 sm:text-sm border-gray-300 rounded-md dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
||||
placeholder="Ulangi password di atas">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-2">
|
||||
<button type="button" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
|
||||
class="text-sm font-medium text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition duration-150 ease-in-out">
|
||||
← Keluar (Batal)
|
||||
</button>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full sm:w-auto flex justify-center py-2 px-6 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150 ease-in-out">
|
||||
Simpan & Masuk
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="hidden">
|
||||
@csrf
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('layouts.guest')] class extends Component
|
||||
{
|
||||
public string $email = '';
|
||||
|
||||
/**
|
||||
* Send a password reset link to the provided email address.
|
||||
*/
|
||||
public function sendPasswordResetLink(): void
|
||||
{
|
||||
$this->validate([
|
||||
'email' => ['required', 'string', 'email'],
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$status = Password::sendResetLink(
|
||||
$this->only('email')
|
||||
);
|
||||
|
||||
if ($status != Password::RESET_LINK_SENT) {
|
||||
$this->addError('email', __($status));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->reset('email');
|
||||
|
||||
session()->flash('status', __($status));
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
</div>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
|
||||
<form wire:submit="sendPasswordResetLink">
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input wire:model="email" id="email" class="block mt-1 w-full" type="email" name="email" required autofocus />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Email Password Reset Link') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Forms\LoginForm;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use function Livewire\Volt\{form, layout};
|
||||
|
||||
// 1. Definisikan Layout
|
||||
layout('layouts.guest');
|
||||
|
||||
// 2. Hubungkan dengan LoginForm class yang sudah kita sempurnakan
|
||||
form(LoginForm::class);
|
||||
|
||||
// 3. Definisikan aksi "login" yang dipanggil oleh form
|
||||
$login = function () {
|
||||
$this->validate();
|
||||
|
||||
// Jalankan autentikasi dari LoginForm
|
||||
$this->form->authenticate();
|
||||
|
||||
// Regenerasi session untuk keamanan
|
||||
Session::regenerate();
|
||||
|
||||
// Ambil data user beserta relasi roles-nya
|
||||
$user = auth()->user();
|
||||
|
||||
// Redirect otomatis berdasarkan Role (RBAC)
|
||||
if ($user->hasRole('admin') || $user->hasRole('trainer')) {
|
||||
$this->redirectIntended(default: route('admin.dashboard', absolute: false), navigate: true);
|
||||
} else {
|
||||
$this->redirectIntended(default: route('cbt.dashboard', absolute: false), navigate: true);
|
||||
}
|
||||
};
|
||||
|
||||
?>
|
||||
|
||||
<div class="min-h-screen w-full flex flex-col lg:flex-row bg-slate-50">
|
||||
<div class="hidden lg:flex lg:w-1/2 bg-gradient-to-br from-slate-900 via-indigo-950 to-slate-900 justify-center items-center p-12 relative overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-10 bg-[radial-gradient(#3b82f6_1px,transparent_1px)] [background-size:16px_16px]"></div>
|
||||
|
||||
<div class="max-w-md w-full relative z-10 text-white">
|
||||
<div class="w-16 h-16 bg-indigo-600 rounded-2xl flex items-center justify-center mb-8 shadow-xl border border-indigo-500/30">
|
||||
<span class="text-3xl font-extrabold tracking-wider">T</span>
|
||||
</div>
|
||||
<h1 class="text-4xl font-extrabold tracking-tight leading-tight mb-4">
|
||||
Learning Management System <span class="text-indigo-400 block mt-2 text-3xl">Version 2.0</span>
|
||||
</h1>
|
||||
<p class="text-slate-400 text-sm leading-relaxed mb-8">
|
||||
Portal pelatihan digital resmi PT Tunggal Idaman Abdi. Tingkatkan kompetensi standarisasi CPOB, SOP, dan validasi industri farmasi secara mandiri dan terukur.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 border-t border-slate-800 pt-8">
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-indigo-400">100%</p>
|
||||
<p class="text-xs text-slate-400 uppercase tracking-wider font-semibold mt-1">Standar CPOB</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-indigo-400">Realtime</p>
|
||||
<p class="text-xs text-slate-400 uppercase tracking-wider font-semibold mt-1">Sertifikasi & Evaluasi</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full lg:w-1/2 flex items-center justify-center p-6 sm:p-12 md:p-16 bg-white">
|
||||
<div class="max-w-md w-full space-y-6">
|
||||
<div class="lg:hidden text-center mb-6">
|
||||
<div class="w-12 h-12 bg-indigo-600 rounded-xl flex items-center justify-center mx-auto mb-3 text-white font-bold text-xl shadow-md">T</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl sm:text-3xl font-bold text-slate-900 tracking-tight">Selamat Datang Kembali</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">Silakan masuk menggunakan akun karyawan Anda untuk memulai pelatihan.</p>
|
||||
</div>
|
||||
|
||||
@if ($errors->has('form.email'))
|
||||
<div class="bg-red-50 border-l-4 border-red-500 p-4 rounded-xl flex items-start space-x-3 transition-all">
|
||||
<svg class="w-5 h-5 text-red-500 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
||||
</svg>
|
||||
<div class="text-sm text-red-700 font-medium">
|
||||
{{ $errors->first('form.email') }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form wire:submit="login" class="space-y-4">
|
||||
<div>
|
||||
<label for="email" class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1.5">Email Perusahaan</label>
|
||||
<input wire:model="form.email" id="email" type="email" required autofocus placeholder="nama@tia-pharma.com"
|
||||
class="w-full px-4 py-2.5 bg-slate-50 border @error('form.email') border-red-300 bg-red-50/30 focus:border-red-500 focus:ring-red-500/20 @else border-slate-200 focus:border-indigo-600 focus:ring-indigo-500/20 @enderror rounded-xl focus:bg-white transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-1.5">
|
||||
<label for="password" class="block text-xs font-semibold uppercase tracking-wider text-slate-600">Password</label>
|
||||
@if (Route::has('password.request'))
|
||||
<a href="{{ route('password.request') }}" class="text-xs font-medium text-indigo-600 hover:text-indigo-700 transition-colors">Lupa Password?</a>
|
||||
@endif
|
||||
</div>
|
||||
<input wire:model="form.password" id="password" type="password" required placeholder="••••••••"
|
||||
class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all text-sm">
|
||||
@error('form.password') <span class="text-xs text-red-500 mt-1 block font-medium">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="flex items-center pt-1">
|
||||
<input wire:model="form.remember" id="remember" type="checkbox" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500/20 border-slate-300 rounded-md">
|
||||
<label for="remember" class="ml-2 block text-sm text-slate-600 font-medium">Ingat perangkat ini</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full mt-2 bg-indigo-600 text-white py-2.5 px-4 rounded-xl font-semibold text-sm hover:bg-indigo-700 active:scale-[0.99] transition-all shadow-md shadow-indigo-600/10 hover:shadow-indigo-600/20 flex justify-center items-center">
|
||||
<span>Masuk ke Dashboard</span>
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="text-center text-sm text-slate-500 pt-4 border-t border-slate-100">
|
||||
Karyawan baru? <a href="{{ route('register') }}" class="text-indigo-600 font-bold hover:underline">Registrasi Akun Mandiri</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,83 @@
|
||||
<div class="min-h-screen flex bg-slate-50">
|
||||
<div class="hidden lg:flex lg:w-1/3 bg-gradient-to-br from-indigo-950 via-slate-900 to-indigo-950 justify-center items-center p-12 relative overflow-hidden">
|
||||
<div class="absolute inset-0 opacity-5 bg-[radial-gradient(#fff_1px,transparent_1px)] [background-size:24px_24px]"></div>
|
||||
|
||||
<div class="max-w-xs w-full relative z-10 text-white">
|
||||
<h3 class="text-2xl font-bold mb-4 tracking-tight">Pendaftaran Mandiri Karyawan</h3>
|
||||
<p class="text-slate-400 text-sm leading-relaxed mb-6">
|
||||
Pastikan Anda menginput **NIK (Nomor Induk Karyawan)** yang valid sesuai dengan data HRD agar proses sinkronisasi matriks pelatihan otomatis berjalan dengan benar.
|
||||
</p>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start text-xs text-slate-300">
|
||||
<div class="w-5 h-5 rounded-full bg-indigo-500/20 text-indigo-400 flex items-center justify-center shrink-0 mr-3 font-bold">1</div>
|
||||
<p>Input NIK & Data Diri lengkap sesuai KTP/ID Card.</p>
|
||||
</div>
|
||||
<div class="flex items-start text-xs text-slate-300">
|
||||
<div class="w-5 h-5 rounded-full bg-indigo-500/20 text-indigo-400 flex items-center justify-center shrink-0 mr-3 font-bold">2</div>
|
||||
<p>Gunakan email internal korporat yang aktif.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full lg:w-2/3 flex items-center justify-center p-8 sm:p-12 bg-white overflow-y-auto">
|
||||
<div class="max-w-xl w-full space-y-6">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-slate-900 tracking-tight">Buat Akun LMS Baru</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Lengkapi formulir di bawah ini untuk mendaftarkan hak akses Anda ke dalam sistem.</p>
|
||||
</div>
|
||||
|
||||
<form wire:submit="register" class="space-y-5">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">NIK (No. Induk Karyawan)</label>
|
||||
<input wire:model="nik" type="text" required placeholder="Contoh: 20260124" class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all">
|
||||
@error('nik') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">Inisial Nama (3 Huruf)</label>
|
||||
<input wire:model="initial" type="text" placeholder="Contoh: TIA" class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all uppercase">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">Nama Depan</label>
|
||||
<input wire:model="first_name" type="text" required class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">Nama Belakang</label>
|
||||
<input wire:model="last_name" type="text" required class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">Email Resmi</label>
|
||||
<input wire:model="email" type="email" required placeholder="username@tunggal-pharma.com" class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all">
|
||||
@error('email') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">Password Baru</label>
|
||||
<input wire:model="password" type="password" required placeholder="Minimal 8 Karakter" class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white">
|
||||
@error('password') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-slate-600 mb-1">Konfirmasi Password</label>
|
||||
<input wire:model="password_confirmation" type="password" required placeholder="Ulangi Password" class="w-full px-3 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:bg-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-slate-900 text-white py-3 rounded-xl font-semibold text-sm hover:bg-slate-800 transition-all shadow-md mt-2">
|
||||
Daftarkan Akun Karyawan
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center text-sm text-slate-500 pt-4 border-t border-slate-100">
|
||||
Sudah memiliki akses akun? <a href="{{ route('login') }}" class="text-indigo-600 font-bold hover:underline">Masuk di sini</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('layouts.guest')] class extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public string $token = '';
|
||||
public string $email = '';
|
||||
public string $password = '';
|
||||
public string $password_confirmation = '';
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*/
|
||||
public function mount(string $token): void
|
||||
{
|
||||
$this->token = $token;
|
||||
|
||||
$this->email = request()->string('email');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the password for the given user.
|
||||
*/
|
||||
public function resetPassword(): void
|
||||
{
|
||||
$this->validate([
|
||||
'token' => ['required'],
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$this->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($this->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status != Password::PASSWORD_RESET) {
|
||||
$this->addError('email', __($status));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Session::flash('status', __($status));
|
||||
|
||||
$this->redirectRoute('login', navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<form wire:submit="resetPassword">
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input wire:model="email" id="email" class="block mt-1 w-full" type="email" name="email" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
<x-text-input wire:model="password" id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input wire:model="password_confirmation" id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Reset Password') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Actions\Logout;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('layouts.guest')] class extends Component
|
||||
{
|
||||
/**
|
||||
* Send an email verification notification to the user.
|
||||
*/
|
||||
public function sendVerification(): void
|
||||
{
|
||||
if (Auth::user()->hasVerifiedEmail()) {
|
||||
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
Session::flash('status', 'verification-link-sent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the current user out of the application.
|
||||
*/
|
||||
public function logout(Logout $logout): void
|
||||
{
|
||||
$logout();
|
||||
|
||||
$this->redirect('/', navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||
</div>
|
||||
|
||||
@if (session('status') == 'verification-link-sent')
|
||||
<div class="mb-4 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<x-primary-button wire:click="sendVerification">
|
||||
{{ __('Resend Verification Email') }}
|
||||
</x-primary-button>
|
||||
|
||||
<button wire:click="logout" type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Log Out') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Actions\Logout;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public string $password = '';
|
||||
|
||||
/**
|
||||
* Delete the currently authenticated user.
|
||||
*/
|
||||
public function deleteUser(Logout $logout): void
|
||||
{
|
||||
$this->validate([
|
||||
'password' => ['required', 'string', 'current_password'],
|
||||
]);
|
||||
|
||||
tap(Auth::user(), $logout(...))->delete();
|
||||
|
||||
$this->redirect('/', navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<section class="space-y-6">
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Delete Account') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<x-danger-button
|
||||
x-data=""
|
||||
x-on:click.prevent="$dispatch('open-modal', 'confirm-user-deletion')"
|
||||
>{{ __('Delete Account') }}</x-danger-button>
|
||||
|
||||
<x-modal name="confirm-user-deletion" :show="$errors->isNotEmpty()" focusable>
|
||||
<form wire:submit="deleteUser" class="p-6">
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Are you sure you want to delete your account?') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="password" value="{{ __('Password') }}" class="sr-only" />
|
||||
|
||||
<x-text-input
|
||||
wire:model="password"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
class="mt-1 block w-3/4"
|
||||
placeholder="{{ __('Password') }}"
|
||||
/>
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button x-on:click="$dispatch('close')">
|
||||
{{ __('Cancel') }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-danger-button class="ms-3">
|
||||
{{ __('Delete Account') }}
|
||||
</x-danger-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
</section>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public string $current_password = '';
|
||||
public string $password = '';
|
||||
public string $password_confirmation = '';
|
||||
|
||||
/**
|
||||
* Update the password for the currently authenticated user.
|
||||
*/
|
||||
public function updatePassword(): void
|
||||
{
|
||||
try {
|
||||
$validated = $this->validate([
|
||||
'current_password' => ['required', 'string', 'current_password'],
|
||||
'password' => ['required', 'string', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
$this->reset('current_password', 'password', 'password_confirmation');
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Auth::user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
$this->reset('current_password', 'password', 'password_confirmation');
|
||||
|
||||
$this->dispatch('password-updated');
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Update Password') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __('Ensure your account is using a long, random password to stay secure.') }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form wire:submit="updatePassword" class="mt-6 space-y-6">
|
||||
<div>
|
||||
<x-input-label for="update_password_current_password" :value="__('Current Password')" />
|
||||
<x-text-input wire:model="current_password" id="update_password_current_password" name="current_password" type="password" class="mt-1 block w-full" autocomplete="current-password" />
|
||||
<x-input-error :messages="$errors->get('current_password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_password" :value="__('New Password')" />
|
||||
<x-text-input wire:model="password" id="update_password_password" name="password" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="update_password_password_confirmation" :value="__('Confirm Password')" />
|
||||
<x-text-input wire:model="password_confirmation" id="update_password_password_confirmation" name="password_confirmation" type="password" class="mt-1 block w-full" autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||
|
||||
<x-action-message class="me-3" on="password-updated">
|
||||
{{ __('Saved.') }}
|
||||
</x-action-message>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public string $name = '';
|
||||
public string $email = '';
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->name = Auth::user()->name;
|
||||
$this->email = Auth::user()->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the profile information for the currently authenticated user.
|
||||
*/
|
||||
public function updateProfileInformation(): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $this->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($user->id)],
|
||||
]);
|
||||
|
||||
$user->fill($validated);
|
||||
|
||||
if ($user->isDirty('email')) {
|
||||
$user->email_verified_at = null;
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
$this->dispatch('profile-updated', name: $user->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email verification notification to the current user.
|
||||
*/
|
||||
public function sendVerification(): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user->hasVerifiedEmail()) {
|
||||
$this->redirectIntended(default: route('dashboard', absolute: false));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
|
||||
Session::flash('status', 'verification-link-sent');
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-medium text-gray-900">
|
||||
{{ __('Profile Information') }}
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600">
|
||||
{{ __("Update your account's profile information and email address.") }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form wire:submit="updateProfileInformation" class="mt-6 space-y-6">
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input wire:model="name" id="name" name="name" type="text" class="mt-1 block w-full" required autofocus autocomplete="name" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input wire:model="email" id="email" name="email" type="email" class="mt-1 block w-full" required autocomplete="username" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||
|
||||
@if (auth()->user() instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! auth()->user()->hasVerifiedEmail())
|
||||
<div>
|
||||
<p class="text-sm mt-2 text-gray-800">
|
||||
{{ __('Your email address is unverified.') }}
|
||||
|
||||
<button wire:click.prevent="sendVerification" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Click here to re-send the verification email.') }}
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@if (session('status') === 'verification-link-sent')
|
||||
<p class="mt-2 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to your email address.') }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Save') }}</x-primary-button>
|
||||
|
||||
<x-action-message class="me-3" on="profile-updated">
|
||||
{{ __('Saved.') }}
|
||||
</x-action-message>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,26 @@
|
||||
<nav class="-mx-3 flex flex-1 justify-end">
|
||||
@auth
|
||||
<a
|
||||
href="{{ url('/dashboard') }}"
|
||||
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
@else
|
||||
<a
|
||||
href="{{ route('login') }}"
|
||||
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
|
||||
>
|
||||
Log in
|
||||
</a>
|
||||
|
||||
@if (Route::has('register'))
|
||||
<a
|
||||
href="{{ route('register') }}"
|
||||
class="rounded-md px-3 py-2 text-black ring-1 ring-transparent transition hover:text-black/70 focus:outline-none focus-visible:ring-[#FF2D20] dark:text-white dark:hover:text-white/80 dark:focus-visible:ring-white"
|
||||
>
|
||||
Register
|
||||
</a>
|
||||
@endif
|
||||
@endauth
|
||||
</nav>
|
||||
@@ -0,0 +1,47 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Dashboard HR - LMS V2.0')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="mb-8 flex justify-between items-end">
|
||||
<div>
|
||||
<h1 class="text-3xl font-extrabold text-slate-900 tracking-tight">Statistik Pelatihan CPOB</h1>
|
||||
<p class="text-sm text-slate-500 mt-1">Ringkasan performa dan kepatuhan training karyawan bulan ini.</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="bg-indigo-600 text-white px-4 py-2 rounded-md shadow hover:bg-indigo-700 text-sm font-semibold">
|
||||
+ Buat Ujian Baru
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
|
||||
<p class="text-xs font-bold text-slate-400 uppercase">Total Karyawan</p>
|
||||
<h3 class="text-3xl font-bold text-slate-900 mt-1">{{ $statistics['total_karyawan'] }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
|
||||
<p class="text-xs font-bold text-slate-400 uppercase">Trainer Internal</p>
|
||||
<h3 class="text-3xl font-bold text-slate-900 mt-1">{{ $statistics['total_trainer'] }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
|
||||
<p class="text-xs font-bold text-slate-400 uppercase">SOP Aktif</p>
|
||||
<h3 class="text-3xl font-bold text-slate-900 mt-1">{{ $statistics['total_sop'] }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
|
||||
<p class="text-xs font-bold text-slate-400 uppercase">Total Kelulusan</p>
|
||||
<h3 class="text-3xl font-bold text-emerald-600 mt-1">{{ $statistics['lulus_ujian'] }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||
<h3 class="text-lg font-bold text-slate-900 mb-4">Matriks Kepatuhan SOP Departemen</h3>
|
||||
|
||||
@livewire('matrix.compliance-monitor')
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,36 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-3xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.departments.index') }}" class="text-slate-400 hover:text-indigo-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Tambah Departemen Baru</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ route('admin.departments.store') }}" method="POST" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-bold text-slate-700 mb-2">Nama Departemen <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name') }}" required placeholder="Contoh: Quality Assurance"
|
||||
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all">
|
||||
@error('name') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-bold text-slate-700 mb-2">Kode Departemen</label>
|
||||
<input type="text" name="code" id="code" value="{{ old('code') }}" placeholder="Contoh: QA"
|
||||
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all uppercase">
|
||||
</div>
|
||||
|
||||
<div class="pt-4 flex justify-end space-x-3 border-t border-slate-100">
|
||||
<a href="{{ route('admin.departments.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-indigo-600 hover:bg-indigo-700 rounded-xl shadow-md transition-all">Simpan Departemen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,37 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-3xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.departments.index') }}" class="text-slate-400 hover:text-indigo-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Departemen Baru</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ route('admin.departments.update', $department->id) }}" method="POST" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-bold text-slate-700 mb-2">Nama Departemen <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name', $department->name) }}" required placeholder="Contoh: Quality Assurance"
|
||||
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all">
|
||||
@error('name') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="code" class="block text-sm font-bold text-slate-700 mb-2">Kode Departemen</label>
|
||||
<input type="text" name="code" id="code" value="{{ old('code', $department->code) }}" placeholder="Contoh: QA"
|
||||
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all uppercase">
|
||||
</div>
|
||||
|
||||
<div class="pt-4 flex justify-end space-x-3 border-t border-slate-100">
|
||||
<a href="{{ route('admin.departments.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-indigo-600 hover:bg-indigo-700 rounded-xl shadow-md transition-all">Simpan Departemen</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Laporan Data Departemen</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; font-size: 12px; color: #333; }
|
||||
.header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #222; padding-bottom: 10px; }
|
||||
.metadata { text-align: right; font-size: 10px; color: #666; margin-bottom: 20px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 10px; text-align: left; }
|
||||
th { background-color: #f4f4f5; color: #111; text-transform: uppercase; font-size: 11px; }
|
||||
tr:nth-child(even) { background-color: #fafafa; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h2>PT TUNGGAL IDAMAN ABDI (TUNGGAL PHARMA)</h2>
|
||||
<h3>Laporan Master Data: Departemen</h3>
|
||||
</div>
|
||||
|
||||
<div class="metadata">
|
||||
Dicetak pada: <strong>{{ $metadata['download_time'] }}</strong><br>
|
||||
Diunduh oleh: <strong>{{ $metadata['downloaded_by'] }}</strong><br>
|
||||
Total Rekor: <strong>{{ $metadata['total_data'] }} Departemen</strong>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">ID</th>
|
||||
<th width="20%">KODE</th>
|
||||
<th width="70%">NAMA DEPARTEMEN</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($departments as $dept)
|
||||
<tr>
|
||||
<td>{{ $dept->id }}</td>
|
||||
<td>{{ $dept->code ?? '-' }}</td>
|
||||
<td>{{ $dept->name }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"
|
||||
x-data="{
|
||||
showCols: { id: true, code: true, name: true, action: true },
|
||||
exportOpen: false,
|
||||
columnOpen: false
|
||||
}">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Manajemen Departemen</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Total <span class="font-bold text-indigo-600">{{ $departments instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator ? $departments->total() : $departments->count() }}</span> rekor ditemukan.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.departments.create') }}" class="inline-flex items-center justify-center px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-semibold hover:bg-indigo-700 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||||
Tambah Departemen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded-t-2xl border border-slate-200 border-b-0 flex flex-col md:flex-row gap-4 justify-between items-center relative z-10">
|
||||
|
||||
<form action="{{ route('admin.departments.index') }}" method="GET" class="w-full md:w-96 relative">
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="Cari nama atau kode..."
|
||||
class="w-full pl-10 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all">
|
||||
<svg class="w-4 h-4 text-slate-400 absolute left-3.5 top-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
@if(request('search'))
|
||||
<a href="{{ route('admin.departments.index') }}" class="absolute right-3 top-2.5 text-xs text-red-500 hover:underline">Clear</a>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<div class="flex items-center space-x-3 w-full md:w-auto">
|
||||
<div class="relative">
|
||||
<button @click="columnOpen = !columnOpen" @click.away="columnOpen = false" class="px-4 py-2 bg-white border border-slate-200 text-slate-700 rounded-xl text-sm font-semibold hover:bg-slate-50 flex items-center shadow-sm">
|
||||
<svg class="w-4 h-4 mr-2 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"></path></svg>
|
||||
Kolom
|
||||
</button>
|
||||
<div x-show="columnOpen" x-transition class="absolute right-0 mt-2 w-48 bg-white border border-slate-100 rounded-xl shadow-xl py-2 z-50" style="display: none;">
|
||||
<label class="flex items-center px-4 py-2 hover:bg-slate-50 cursor-pointer text-sm"><input type="checkbox" x-model="showCols.id" class="rounded border-slate-300 text-indigo-600 mr-3"> ID</label>
|
||||
<label class="flex items-center px-4 py-2 hover:bg-slate-50 cursor-pointer text-sm"><input type="checkbox" x-model="showCols.code" class="rounded border-slate-300 text-indigo-600 mr-3"> Kode</label>
|
||||
<label class="flex items-center px-4 py-2 hover:bg-slate-50 cursor-pointer text-sm"><input type="checkbox" x-model="showCols.name" class="rounded border-slate-300 text-indigo-600 mr-3"> Nama Departemen</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<button @click="exportOpen = !exportOpen" @click.away="exportOpen = false" class="px-4 py-2 bg-emerald-50 border border-emerald-200 text-emerald-700 rounded-xl text-sm font-semibold hover:bg-emerald-100 flex items-center transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
||||
Export Data
|
||||
</button>
|
||||
<div x-show="exportOpen" x-transition class="absolute right-0 mt-2 w-48 bg-white border border-slate-100 rounded-xl shadow-xl py-2 z-50" style="display: none;">
|
||||
<p class="px-4 py-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Pilih Format</p>
|
||||
<a href="{{ url('admin/departments/export/excel?search=' . request('search')) }}" class="flex items-center px-4 py-2 hover:bg-slate-50 text-sm text-slate-700">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 mr-2"></span> Excel (.xlsx)
|
||||
</a>
|
||||
<a href="{{ url('admin/departments/export/pdf?search=' . request('search')) }}" class="flex items-center px-4 py-2 hover:bg-slate-50 text-sm text-slate-700">
|
||||
<span class="w-2 h-2 rounded-full bg-red-500 mr-2"></span> PDF (.pdf)
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-b-2xl overflow-hidden shadow-sm relative z-0">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50/80 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th x-show="showCols.id" class="px-6 py-4 font-semibold">ID</th>
|
||||
<th x-show="showCols.code" class="px-6 py-4 font-semibold">Kode</th>
|
||||
<th x-show="showCols.name" class="px-6 py-4 font-semibold">Nama Departemen</th>
|
||||
<th x-show="showCols.action" class="px-6 py-4 font-semibold text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($departments as $dept)
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td x-show="showCols.id" class="px-6 py-4 font-medium text-slate-500">#{{ $dept->id }}</td>
|
||||
<td x-show="showCols.code" class="px-6 py-4">
|
||||
<span class="bg-slate-100 text-slate-600 px-2.5 py-1 rounded-md text-xs font-bold border border-slate-200">{{ $dept->code ?? 'N/A' }}</span>
|
||||
</td>
|
||||
<td x-show="showCols.name" class="px-6 py-4 font-bold text-slate-900">{{ $dept->name }}</td>
|
||||
<td x-show="showCols.action" class="px-6 py-4 text-right space-x-3">
|
||||
<a href="{{ route('admin.departments.show', $dept->id) }}" class="text-slate-400 hover:text-indigo-600 font-medium text-sm transition-colors">Detail</a>
|
||||
<a href="{{ route('admin.departments.edit', $dept->id) }}" class="text-indigo-600 hover:text-indigo-800 font-medium text-sm transition-colors">Edit</a>
|
||||
|
||||
<form action="{{ route('admin.departments.destroy', $dept->id) }}" method="POST" class="inline-block" onsubmit="return confirm('Apakah Anda yakin ingin menghapus departemen ini?');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="text-red-500 hover:text-red-700 font-medium text-sm transition-colors">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-12 text-center text-slate-500">
|
||||
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path></svg>
|
||||
Tidak ada data departemen yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(method_exists($departments, 'hasPages') && $departments->hasPages())
|
||||
<div class="p-4 border-t border-slate-100 bg-slate-50/50">
|
||||
{{ $departments->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,141 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Detail Departemen</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Informasi lengkap mengenai departemen.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.departments.index') }}" class="inline-flex items-center px-4 py-2 bg-slate-100 text-slate-700 rounded-lg text-sm font-semibold hover:bg-slate-200 transition-colors">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="p-6 sm:p-10">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-800 mb-4 pb-2 border-b border-slate-100">Informasi Utama</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-slate-500 mb-1">Kode Departemen</span>
|
||||
<div class="inline-block bg-slate-100 text-slate-700 px-3 py-1 rounded-md text-sm font-bold border border-slate-200">
|
||||
{{ $department->code ?? '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-slate-500 mb-1">Nama Departemen</span>
|
||||
<div class="text-lg font-bold text-slate-900">
|
||||
{{ $department->name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-800 mb-4 pb-2 border-b border-slate-100">Informasi Sistem</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-slate-500 mb-1">ID Sistem</span>
|
||||
<div class="text-sm text-slate-900 font-mono bg-slate-50 inline-block px-2 py-1 rounded border border-slate-100">
|
||||
#{{ $department->id }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-slate-500 mb-1">Tanggal Dibuat</span>
|
||||
<div class="text-sm text-slate-900">
|
||||
{{ $department->created_at ? $department->created_at->format('d F Y, H:i') : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-slate-500 mb-1">Terakhir Diperbarui</span>
|
||||
<div class="text-sm text-slate-900">
|
||||
{{ $department->updated_at ? $department->updated_at->diffForHumans() : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 pt-8 border-t border-slate-100">
|
||||
<h3 class="text-lg font-semibold text-slate-800 mb-6">Pemetaan & Statistik</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="bg-indigo-50 border border-indigo-100 rounded-xl p-6 flex items-center">
|
||||
<div class="p-3 bg-indigo-100 text-indigo-600 rounded-lg mr-4">
|
||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-slate-500">Total Karyawan Aktif</p>
|
||||
<p class="text-2xl font-bold text-slate-900">{{ $department->users()->count() }} Orang</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 border border-slate-100 rounded-xl p-6">
|
||||
<p class="text-sm font-medium text-slate-500 mb-3">Posisi / Jabatan pada Departemen ini ({{ $department->positions()->count() }})</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@forelse($department->positions as $pos)
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-white border border-slate-200 text-slate-700 shadow-sm">
|
||||
{{ $pos->name }}
|
||||
</span>
|
||||
@empty
|
||||
<span class="text-sm text-slate-400 italic">Belum ada posisi yang dipetakan.</span>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 pt-8 border-t border-slate-100">
|
||||
<h3 class="text-lg font-semibold text-slate-800 mb-4">Daftar Karyawan di Departemen Ini</h3>
|
||||
<div class="bg-white border border-slate-200 rounded-xl overflow-hidden shadow-sm">
|
||||
<div class="overflow-x-auto custom-scrollbar">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="px-6 py-3 font-semibold">NIK</th>
|
||||
<th class="px-6 py-3 font-semibold">Nama Karyawan</th>
|
||||
<th class="px-6 py-3 font-semibold">Jabatan</th>
|
||||
<th class="px-6 py-3 font-semibold text-right">Opsi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($department->users as $user)
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-6 py-3 font-mono text-xs font-bold">{{ $user->nik ?? '-' }}</td>
|
||||
<td class="px-6 py-3">
|
||||
<div class="font-bold text-slate-900">{{ $user->first_name }} {{ $user->last_name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ $user->email }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-3 text-sm">{{ $user->position->name ?? '-' }}</td>
|
||||
<td class="px-6 py-3 text-right">
|
||||
<a href="{{ route('admin.employees.show', $user->id) }}" class="text-indigo-600 hover:text-indigo-800 font-medium text-xs">Detail</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-8 text-center text-slate-500 italic">Belum ada karyawan di departemen ini.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 pt-6 border-t border-slate-100 flex items-center justify-end space-x-3">
|
||||
<a href="{{ route('admin.departments.edit', $department->id) }}" class="inline-flex items-center px-4 py-2 bg-indigo-50 text-indigo-700 rounded-lg text-sm font-semibold hover:bg-indigo-100 transition-colors">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
||||
Edit Departemen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,238 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
/* Penyesuaian UI Select2 agar senada dengan Tailwind dan menyembunyikan elemen asli */
|
||||
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
|
||||
.select2-hidden-accessible { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Tambah Karyawan Baru</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="bg-blue-50/50 p-4 border-b border-slate-100 flex items-start">
|
||||
<svg class="w-5 h-5 text-blue-600 mt-0.5 mr-3 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<p class="text-sm text-blue-800">Lengkapi formulir di bawah ini. Password default: <span class="font-mono font-bold bg-white px-2 py-0.5 rounded border border-blue-200">12345678</span></p>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('admin.employees.store') }}" method="POST" enctype="multipart/form-data" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">NIK <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nik" value="{{ old('nik') }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
@error('nik') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. KTP</label>
|
||||
<input type="text" name="identity_number" value="{{ old('identity_number') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Inisial <span class="text-blue-500 font-normal text-xs">(Auto Suggest)</span></label>
|
||||
<input type="text" name="initial" id="initial" value="{{ old('initial') }}" maxlength="3" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm uppercase">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Depan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="first_name" id="first_name" value="{{ old('first_name') }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
@error('first_name') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Belakang</label>
|
||||
<input type="text" name="last_name" id="last_name" value="{{ old('last_name') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Email Perusahaan <span class="text-red-500">*</span></label>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
@error('email') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. HP / WA</label>
|
||||
<input type="text" name="phone" value="{{ old('phone') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Jenis Kelamin <span class="text-red-500">*</span></label>
|
||||
<select name="gender" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih --</option>
|
||||
<option value="L" {{ old('gender') == 'L' ? 'selected' : '' }}>Laki-laki</option>
|
||||
<option value="P" {{ old('gender') == 'P' ? 'selected' : '' }}>Perempuan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Lahir</label>
|
||||
<input type="date" name="date_of_birth" value="{{ old('date_of_birth') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Bergabung</label>
|
||||
<input type="date" name="join_date" value="{{ old('join_date') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Departemen <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="deptSelect" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Departemen --</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id') == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan <span class="text-red-500">*</span></label>
|
||||
<select name="position_id" id="posSelect" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Dept Terlebih Dahulu --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Hak Akses Sistem Utama <span class="text-red-500">*</span></label>
|
||||
<select name="role" class="select2 w-full text-sm" required>
|
||||
<option value="karyawan" {{ old('role') == 'karyawan' ? 'selected' : '' }}>Trainee (Karyawan)</option>
|
||||
@if(auth()->user()->hasRole('superadmin'))
|
||||
<option value="admin" {{ old('role') == 'admin' ? 'selected' : '' }}>Administrator</option>
|
||||
@endif
|
||||
</select>
|
||||
|
||||
<label class="flex items-center space-x-2 cursor-pointer mt-4">
|
||||
<input type="checkbox" name="is_trainer" value="1" {{ old('is_trainer') ? 'checked' : '' }} class="w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500">
|
||||
<span class="text-sm font-semibold text-slate-700">Jadikan Trainer / Pemateri juga</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Modul SOP Training (Marketing)</label>
|
||||
<select name="training_matrix_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Kosongkan jika bukan Plant Dept --</option>
|
||||
@foreach($trainingMatrices as $matrix)
|
||||
<option value="{{ $matrix->id }}" {{ old('training_matrix_id') == $matrix->id ? 'selected' : '' }}>{{ $matrix->title ?? $matrix->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6" x-data="{ documents: [{ id: 1 }], addDocument() { this.documents.push({ id: Date.now() }); }, removeDocument(id) { this.documents = this.documents.filter(doc => doc.id !== id); } }">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700">Dokumen Karyawan</label>
|
||||
<p class="text-xs text-slate-500 mt-1">CV, KTP, Ijazah (Otomatis terunggah ke Nextcloud)</p>
|
||||
</div>
|
||||
<button type="button" @click="addDocument()" class="text-xs bg-blue-50 text-blue-700 font-bold px-3 py-1.5 rounded-lg hover:bg-blue-100">
|
||||
+ Tambah Kolom File
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<template x-for="(doc, index) in documents" :key="doc.id">
|
||||
<div class="flex items-center gap-3 bg-slate-50 p-3 rounded-lg border border-slate-200">
|
||||
<span class="text-sm font-bold text-slate-400 w-6" x-text="index + 1 + '.'"></span>
|
||||
<input type="file" name="documents[]" class="flex-1 text-sm file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer">
|
||||
<button type="button" x-show="documents.length > 1" @click="removeDocument(doc.id)" class="text-red-500 hover:text-red-700 p-2">
|
||||
Hapus
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 flex justify-end space-x-3 border-t border-slate-100 mt-8">
|
||||
<a href="{{ route('admin.employees.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-md transition-all">Simpan Karyawan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// 1. Inisialisasi Select2 di semua dropdown yang ada class select2
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
// 2. Logika Dropdown Bertingkat (Dept -> Posisi)
|
||||
const mapping = @json($deptPosMapping ?? []);
|
||||
const posSelect = $('#posSelect');
|
||||
const deptSelect = $('#deptSelect');
|
||||
const oldPos = "{{ old('position_id') }}";
|
||||
|
||||
function updatePositionDropdown() {
|
||||
let deptId = deptSelect.val();
|
||||
posSelect.empty();
|
||||
posSelect.append(new Option('-- Pilih Jabatan --', ''));
|
||||
|
||||
if (deptId && mapping[deptId]) {
|
||||
mapping[deptId].forEach(function(pos) {
|
||||
let selected = (pos.id == oldPos);
|
||||
posSelect.append(new Option(pos.name, pos.id, false, selected));
|
||||
});
|
||||
} else {
|
||||
// Munculkan semua jika Dept belum terpilih
|
||||
@foreach($positions as $p)
|
||||
posSelect.append(new Option("{{ $p->name }}", "{{ $p->id }}", false, ("{{ $p->id }}" == oldPos)));
|
||||
@endforeach
|
||||
}
|
||||
posSelect.trigger('change.select2');
|
||||
}
|
||||
|
||||
deptSelect.on('change', updatePositionDropdown);
|
||||
updatePositionDropdown(); // Eksekusi saat load pertama kali
|
||||
});
|
||||
|
||||
// 3. FITUR AUTO-SUGGEST INITIAL 3 DIGIT
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const existingInitials = @json($existingInitials ?? []);
|
||||
const firstNameInput = document.getElementById('first_name');
|
||||
const lastNameInput = document.getElementById('last_name');
|
||||
const initialInput = document.getElementById('initial');
|
||||
|
||||
function generateInitial() {
|
||||
let first = firstNameInput.value.trim().toLowerCase();
|
||||
let last = lastNameInput.value.trim().toLowerCase();
|
||||
let fullName = (first + ' ' + last).trim();
|
||||
|
||||
if (fullName.length === 0) return;
|
||||
|
||||
let words = fullName.split(/\s+/).filter(w => w.length > 0);
|
||||
let initial = '';
|
||||
|
||||
// Algoritma: 3 kata ambil huruf pertama, 2 kata ambil 1 dpn & 2 blkg, 1 kata ambil 3 awal.
|
||||
if (words.length >= 3) {
|
||||
initial = words[0][0] + words[1][0] + words[2][0];
|
||||
} else if (words.length === 2) {
|
||||
initial = words[0][0] + words[1].substring(0, 2);
|
||||
} else if (words.length === 1) {
|
||||
initial = words[0].substring(0, 3);
|
||||
}
|
||||
|
||||
initial = initial.replace(/[^a-z]/g, '');
|
||||
while(initial.length < 3) { initial += 'x'; }
|
||||
initial = initial.substring(0, 3);
|
||||
|
||||
let baseInitial = initial.substring(0, 2);
|
||||
let counter = 0;
|
||||
let alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
// Cek ke DB: jika inisial sudah ada, ganti huruf ke-3 secara alphabet.
|
||||
while (existingInitials.includes(initial) && counter < 26) {
|
||||
initial = baseInitial + alphabet[counter];
|
||||
counter++;
|
||||
}
|
||||
initialInput.value = initial.toUpperCase();
|
||||
}
|
||||
|
||||
firstNameInput.addEventListener('input', generateInitial);
|
||||
lastNameInput.addEventListener('input', generateInitial);
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,171 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
|
||||
.select2-hidden-accessible { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0,0,0,0) !important; white-space: nowrap !important; border: 0 !important; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Data Karyawan</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ route('admin.employees.update', $employee->id) }}" method="POST" enctype="multipart/form-data" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">NIK <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="nik" value="{{ old('nik', $employee->nik) }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. KTP</label>
|
||||
<input type="text" name="identity_number" value="{{ old('identity_number', $employee->identity_number) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Inisial</label>
|
||||
<input type="text" name="initial" value="{{ old('initial', $employee->initial) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm uppercase">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Depan <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="first_name" value="{{ old('first_name', $employee->first_name) }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Nama Belakang</label>
|
||||
<input type="text" name="last_name" value="{{ old('last_name', $employee->last_name) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Email Perusahaan <span class="text-red-500">*</span></label>
|
||||
<input type="email" name="email" value="{{ old('email', $employee->email) }}" required class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">No. HP / WA</label>
|
||||
<input type="text" name="phone" value="{{ old('phone', $employee->phone) }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Jenis Kelamin <span class="text-red-500">*</span></label>
|
||||
<select name="gender" class="select2 w-full text-sm" required>
|
||||
<option value="L" {{ old('gender', $employee->gender) == 'L' ? 'selected' : '' }}>Laki-laki</option>
|
||||
<option value="P" {{ old('gender', $employee->gender) == 'P' ? 'selected' : '' }}>Perempuan</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Lahir</label>
|
||||
<input type="date" name="date_of_birth" value="{{ old('date_of_birth', $employee->date_of_birth ? \Carbon\Carbon::parse($employee->date_of_birth)->format('Y-m-d') : '') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tanggal Bergabung</label>
|
||||
<input type="date" name="join_date" value="{{ old('join_date', $employee->join_date ? \Carbon\Carbon::parse($employee->join_date)->format('Y-m-d') : '') }}" class="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Departemen <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="deptSelect" class="select2 w-full text-sm" required>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id', $employee->department_id) == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan <span class="text-red-500">*</span></label>
|
||||
<select name="position_id" id="posSelect" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Jabatan --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Hak Akses Sistem Utama <span class="text-red-500">*</span></label>
|
||||
<select name="role" class="select2 w-full text-sm" required>
|
||||
<option value="karyawan" {{ $employee->hasRole('admin') ? '' : 'selected' }}>Trainee (Karyawan)</option>
|
||||
@if(auth()->user()->hasRole('superadmin'))
|
||||
<option value="admin" {{ $employee->hasRole('admin') ? 'selected' : '' }}>Administrator</option>
|
||||
@endif
|
||||
</select>
|
||||
|
||||
<label class="flex items-center space-x-2 cursor-pointer mt-4">
|
||||
<input type="checkbox" name="is_trainer" value="1" {{ $employee->hasRole('trainer') ? 'checked' : '' }} class="w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500">
|
||||
<span class="text-sm font-semibold text-slate-700">Tetapkan sebagai Trainer / Pemateri</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 p-4 border border-slate-200 rounded-xl">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Status Karyawan <span class="text-red-500">*</span></label>
|
||||
<label class="flex items-center space-x-2 cursor-pointer mt-2">
|
||||
<input type="checkbox" name="is_active" value="1" {{ old('is_active', $employee->is_active ?? true) ? 'checked' : '' }} class="w-4 h-4 text-emerald-600 border-slate-300 rounded focus:ring-emerald-500">
|
||||
<span class="text-sm font-semibold text-slate-700">Akun Aktif</span>
|
||||
</label>
|
||||
<p class="text-[11px] text-slate-500 mt-2">Hilangkan centang jika karyawan resign.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Modul SOP Training (Marketing)</label>
|
||||
<select name="training_matrix_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Kosongkan jika bukan marketing --</option>
|
||||
@foreach($trainingMatrices as $matrix)
|
||||
<option value="{{ $matrix->id }}" {{ old('training_matrix_id', $employee->training_matrix_id) == $matrix->id ? 'selected' : '' }}>{{ $matrix->title ?? $matrix->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6 mt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Reset Password (Opsional)</label>
|
||||
<input type="password" name="password" placeholder="Kosongkan jika tidak ingin mengubah password" class="w-full md:w-1/2 px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm">
|
||||
</div>
|
||||
|
||||
<div class="pt-6 flex justify-end space-x-3 border-t border-slate-100 mt-8">
|
||||
<a href="{{ route('admin.employees.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-md transition-all">Perbarui Karyawan</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
// Logika Dropdown Bertingkat untuk Edit (Sama dengan Create)
|
||||
const mapping = @json($deptPosMapping ?? []);
|
||||
const posSelect = $('#posSelect');
|
||||
const deptSelect = $('#deptSelect');
|
||||
const defaultPos = "{{ old('position_id', $employee->position_id) }}";
|
||||
|
||||
function updatePositionDropdown() {
|
||||
let deptId = deptSelect.val();
|
||||
posSelect.empty();
|
||||
posSelect.append(new Option('-- Pilih Jabatan --', ''));
|
||||
|
||||
if (deptId && mapping[deptId]) {
|
||||
mapping[deptId].forEach(function(pos) {
|
||||
let selected = (pos.id == defaultPos);
|
||||
posSelect.append(new Option(pos.name, pos.id, false, selected));
|
||||
});
|
||||
} else {
|
||||
@foreach($positions as $p)
|
||||
posSelect.append(new Option("{{ $p->name }}", "{{ $p->id }}", false, ("{{ $p->id }}" == defaultPos)));
|
||||
@endforeach
|
||||
}
|
||||
posSelect.trigger('change.select2');
|
||||
}
|
||||
|
||||
deptSelect.on('change', updatePositionDropdown);
|
||||
updatePositionDropdown();
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,72 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.import') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Pratinjau Data Import</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 mb-6 rounded-r-lg">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-amber-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" /></svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-amber-800 font-semibold">Tinjau data Anda!</p>
|
||||
<p class="text-sm text-amber-700 mt-1">Data di bawah ini belum tersimpan. Pastikan nama kolom dan isi data sudah sesuai. Baris yang NIK, Nama Depan, atau Email-nya kosong akan dilewati (diabaikan).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden mb-6">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="px-6 py-4 font-semibold">NIK</th>
|
||||
<th class="px-6 py-4 font-semibold">Nama Depan</th>
|
||||
<th class="px-6 py-4 font-semibold">Nama Belakang</th>
|
||||
<th class="px-6 py-4 font-semibold">Email</th>
|
||||
<th class="px-6 py-4 font-semibold">Telepon</th>
|
||||
<th class="px-6 py-4 font-semibold">ID Dept</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@foreach($rows as $index => $row)
|
||||
@if($index < 20) <!-- Batasi preview 20 baris saja agar browser tidak berat -->
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-6 py-3 font-mono font-bold">{{ $row['nik'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['nama_depan'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['nama_belakang'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3 text-blue-600">{{ $row['email'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['telepon'] ?? '-' }}</td>
|
||||
<td class="px-6 py-3">{{ $row['department_id'] ?? '-' }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if(count($rows) > 20)
|
||||
<div class="p-4 text-center text-sm text-slate-500 border-t border-slate-100 bg-slate-50">
|
||||
... Menampilkan 20 baris pertama dari total <b>{{ count($rows) }}</b> baris data.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Tombol Aksi -->
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<a href="{{ route('admin.employees.import') }}" class="px-6 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal & Kembali</a>
|
||||
<form action="{{ route('admin.employees.import.process') }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="px-6 py-2.5 text-sm font-semibold text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl shadow-md transition-all flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>
|
||||
Konfirmasi & Simpan Permanen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,42 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Import Data Karyawan</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Kotak Bantuan Template -->
|
||||
<div class="bg-blue-50 rounded-2xl p-6 border border-blue-100">
|
||||
<h3 class="text-lg font-bold text-blue-900 mb-2">1. Unduh Template</h3>
|
||||
<p class="text-sm text-blue-700 mb-6">Agar proses import berjalan lancar, pastikan Anda menggunakan template Excel/CSV standar dari sistem. Kolom wajib diisi adalah NIK, Nama, dan Email.</p>
|
||||
<a href="{{ route('admin.employees.import.template') }}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
||||
Download Template
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Kotak Upload File -->
|
||||
<div class="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm">
|
||||
<h3 class="text-lg font-bold text-slate-800 mb-2">2. Unggah File Data</h3>
|
||||
<p class="text-sm text-blue-700 mb-6 max-w-lg mx-auto">Gunakan format Excel standar dari sistem. Kolom wajib isi: NIK, Nama Depan, Jenis Kelamin dan Email. Tanggal gunakan format YYYY-MM-DD.</p>
|
||||
|
||||
<form action="{{ route('admin.employees.import.preview') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="mb-6">
|
||||
<input type="file" name="file" accept=".xlsx, .xls, .csv" required class="w-full text-sm text-slate-500 file:mr-4 file:py-2.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 cursor-pointer border border-slate-200 rounded-lg p-1 bg-slate-50">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full flex justify-center items-center px-4 py-2.5 text-sm font-semibold text-white bg-slate-800 hover:bg-slate-900 rounded-lg transition-all shadow-md">
|
||||
Lanjutkan ke Preview
|
||||
<svg class="w-4 h-4 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,288 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0"></script>
|
||||
|
||||
<style>
|
||||
/* Kustomisasi Scrollbar */
|
||||
.custom-scrollbar::-webkit-scrollbar { height: 6px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 4px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"
|
||||
x-data="{ showCols: { nik: true, name: true, role: true, department: true, action: true }, showCharts: false }">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Manajemen Karyawan</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Total <span class="font-bold text-blue-600">{{ $totalRecords }}</span> data karyawan terdaftar.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
|
||||
<button @click="showCharts = !showCharts" class="inline-flex items-center px-4 py-2 bg-slate-100 text-slate-700 border border-slate-200 rounded-lg text-sm font-semibold hover:bg-slate-200 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
|
||||
<span x-text="showCharts ? 'Sembunyikan Grafik' : 'Lihat Grafik'"></span>
|
||||
</button>
|
||||
|
||||
<div x-data="{ exportMenuOpen: false }" class="relative">
|
||||
<button @click="exportMenuOpen = !exportMenuOpen" @click.away="exportMenuOpen = false" class="inline-flex items-center px-4 py-2 bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-lg text-sm font-semibold hover:bg-emerald-100 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
|
||||
Export / Import
|
||||
</button>
|
||||
|
||||
<div x-show="exportMenuOpen" x-transition style="display: none;" class="absolute right-0 mt-2 w-48 bg-white border border-slate-100 rounded-xl shadow-xl py-2 z-50">
|
||||
|
||||
<a href="{{ route('admin.employees.export.excel', request()->query()) }}" class="flex items-center px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 mr-2"></span> Export to Excel
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.employees.export.pdf', request()->query()) }}" target="_blank" class="flex items-center px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
|
||||
<span class="w-2 h-2 rounded-full bg-red-500 mr-2"></span> Export to PDF
|
||||
</a>
|
||||
|
||||
<div class="border-t border-slate-100 my-1"></div>
|
||||
|
||||
<a href="{{ route('admin.employees.import') }}" class="w-full flex items-center px-4 py-2 text-sm text-slate-700 hover:bg-slate-50 text-left">
|
||||
<span class="w-2 h-2 rounded-full bg-blue-500 mr-2"></span> Import dari Excel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{{ route('admin.employees.create') }}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 shadow-sm transition-all">
|
||||
+ Tambah Karyawan
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-show="showCharts" x-transition style="display: none;" class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm">
|
||||
<h3 class="text-sm font-bold text-slate-700 mb-4">Distribusi per Departemen (Geser 👉)</h3>
|
||||
<div class="overflow-x-auto w-full pb-2 custom-scrollbar">
|
||||
<div id="deptChartContainer" style="height: 220px;">
|
||||
<canvas id="deptChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm flex flex-col h-full">
|
||||
<h3 class="text-sm font-bold text-slate-700 mb-4">Distribusi per Jabatan</h3>
|
||||
<div class="relative h-40 w-full mb-4">
|
||||
<canvas id="posChart"></canvas>
|
||||
</div>
|
||||
<div class="overflow-x-auto pb-2 custom-scrollbar mt-auto">
|
||||
<div id="posLegend" class="flex gap-2 whitespace-nowrap min-w-max px-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-t-2xl border border-slate-200 border-b-0 relative z-10">
|
||||
<form action="{{ route('admin.employees.index') }}" method="GET" class="flex flex-col md:flex-row gap-4 items-end">
|
||||
<div class="w-full md:w-1/3">
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pencarian Semua Kolom</label>
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="Ketik keyword apapun..."
|
||||
class="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-indigo-500">
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pilih Departemen</label>
|
||||
<select name="department_id" id="deptSelect" class="select2 w-full">
|
||||
<option value="">Semua Departemen</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ request('department_id') == $dept->id ? 'selected' : '' }}>
|
||||
{{ $dept->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-1/4">
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pilih Jabatan</label>
|
||||
<select name="position_id" id="posSelect" class="select2 w-full">
|
||||
<option value="">Semua Jabatan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-auto flex gap-2">
|
||||
<button type="submit" class="px-5 py-2 bg-slate-800 text-white rounded-xl text-sm font-semibold hover:bg-slate-700">Filter</button>
|
||||
@if(request()->hasAny(['search', 'department_id', 'position_id']))
|
||||
<a href="{{ route('admin.employees.index') }}" class="px-5 py-2 bg-red-50 text-red-600 border border-red-100 rounded-xl text-sm font-semibold hover:bg-red-100">Reset</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-b-2xl overflow-hidden shadow-sm relative z-0 mt-4 md:mt-0">
|
||||
<div class="overflow-x-auto custom-scrollbar">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50/80 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th x-show="showCols.nik" class="px-6 py-4 font-semibold">NIK & Inisial</th>
|
||||
<th x-show="showCols.name" class="px-6 py-4 font-semibold">Profil & Kontak</th>
|
||||
<th x-show="showCols.role" class="px-6 py-4 font-semibold text-center">Hak Akses Sistem</th>
|
||||
<th x-show="showCols.department" class="px-6 py-4 font-semibold">Penempatan</th>
|
||||
<th x-show="showCols.action" class="px-6 py-4 font-semibold text-right">Opsi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($employees as $emp)
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td x-show="showCols.nik" class="px-6 py-4">
|
||||
<div class="font-mono font-bold text-slate-900">{{ $emp->nik ?? '-' }}</div>
|
||||
<div class="mt-1">
|
||||
<span class="bg-indigo-50 text-indigo-700 px-2 py-0.5 rounded text-[10px] font-bold border border-indigo-100">
|
||||
{{ $emp->initial ?? 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.name" class="px-6 py-4">
|
||||
<div class="font-bold text-slate-900">
|
||||
{{ $emp->first_name }} {{ $emp->last_name }}
|
||||
@if(!$emp->is_active)
|
||||
<span class="ml-2 px-2 py-0.5 bg-red-100 text-red-600 text-[10px] font-bold rounded uppercase">Non-Aktif</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-xs text-slate-500">{{ $emp->email }}</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.role" class="px-6 py-4 text-center">
|
||||
<div class="flex flex-wrap gap-1 justify-center">
|
||||
@php
|
||||
$isTrainer = $emp->hasRole('trainer') || $emp->is_trainer == 1 || (isset($emp->role) && strtolower($emp->role) == 'trainer');
|
||||
$isAdmin = $emp->hasRole('admin') || $emp->hasRole('superadmin') || (isset($emp->role) && strtolower($emp->role) == 'admin');
|
||||
@endphp
|
||||
|
||||
@if($isAdmin)
|
||||
<span class="px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide bg-red-100 text-red-700">Admin</span>
|
||||
@endif
|
||||
|
||||
@if($isTrainer)
|
||||
<span class="px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide bg-amber-100 text-amber-700">Trainer</span>
|
||||
@endif
|
||||
|
||||
<span class="px-2 py-1 rounded text-[10px] font-bold uppercase tracking-wide bg-emerald-100 text-emerald-700">Trainee</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.department" class="px-6 py-4">
|
||||
<div class="text-sm font-semibold text-slate-800">{{ $emp->position->name ?? '-' }}</div>
|
||||
<div class="text-xs text-slate-500 mt-0.5">{{ $emp->department->name ?? '-' }}</div>
|
||||
</td>
|
||||
|
||||
<td x-show="showCols.action" class="px-6 py-4 text-right space-x-2">
|
||||
<a href="{{ route('admin.employees.show', $emp->id) }}" class="text-slate-400 hover:text-indigo-600 font-medium text-sm">Detail</a>
|
||||
<a href="{{ route('admin.employees.edit', $emp->id) }}" class="text-indigo-600 hover:text-indigo-800 font-medium text-sm">Edit</a>
|
||||
|
||||
<form action="{{ route('admin.employees.destroy', $emp->id) }}" method="POST" class="inline-block" onsubmit="return confirm('Nonaktifkan karyawan ini? Data historis tidak akan dihapus.');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="text-red-500 hover:text-red-700 font-medium text-sm ml-2">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-10 text-center text-slate-500 italic">Data tidak ditemukan.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(method_exists($employees, 'hasPages') && $employees->hasPages())
|
||||
<div class="p-4 border-t border-slate-100 bg-slate-50/50">
|
||||
{{ $employees->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
const mapping = @json($deptPosMapping ?? []);
|
||||
const posSelect = $('#posSelect');
|
||||
const deptSelect = $('#deptSelect');
|
||||
const oldPos = "{{ request('position_id') }}";
|
||||
|
||||
function updatePositionDropdown() {
|
||||
let deptId = deptSelect.val();
|
||||
posSelect.empty();
|
||||
posSelect.append(new Option('Semua Jabatan', ''));
|
||||
|
||||
if (deptId && mapping[deptId]) {
|
||||
mapping[deptId].forEach(function(pos) {
|
||||
let selected = (pos.id == oldPos);
|
||||
posSelect.append(new Option(pos.name, pos.id, false, selected));
|
||||
});
|
||||
} else {
|
||||
@foreach($positions as $p)
|
||||
posSelect.append(new Option("{{ $p->name }}", "{{ $p->id }}", false, ("{{ $p->id }}" == oldPos)));
|
||||
@endforeach
|
||||
}
|
||||
posSelect.trigger('change.select2');
|
||||
}
|
||||
|
||||
deptSelect.on('change', updatePositionDropdown);
|
||||
updatePositionDropdown();
|
||||
});
|
||||
|
||||
// KONFIGURASI GRAFIK
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
Chart.register(ChartDataLabels);
|
||||
const chartColors = ['#4F46E5', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4', '#14B8A6', '#F43F5E', '#84CC16', '#3B82F6', '#F97316', '#6366F1'];
|
||||
|
||||
const rawDeptData = @json($chartDept ?? []);
|
||||
const deptLabels = rawDeptData.map(d => d.department ? d.department.name : 'N/A');
|
||||
const deptCounts = rawDeptData.map(d => d.total);
|
||||
const deptBgColors = rawDeptData.map((d, i) => chartColors[i % chartColors.length]);
|
||||
|
||||
const minDeptWidth = Math.max(600, rawDeptData.length * 60);
|
||||
document.getElementById('deptChartContainer').style.width = minDeptWidth + 'px';
|
||||
|
||||
new Chart(document.getElementById('deptChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: deptLabels,
|
||||
datasets: [{ data: deptCounts, backgroundColor: deptBgColors, borderRadius: 4 }]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false }, datalabels: { color: '#1e293b', anchor: 'end', align: 'top', font: { weight: 'bold' } } },
|
||||
scales: { y: { beginAtZero: true, grace: '10%' }, x: { ticks: { autoSkip: false, maxRotation: 45, minRotation: 45 } } }
|
||||
}
|
||||
});
|
||||
|
||||
const rawPosData = @json($chartPos ?? []);
|
||||
const posLabels = rawPosData.map(d => d.position ? d.position.name : 'N/A');
|
||||
const posCounts = rawPosData.map(d => d.total);
|
||||
const posBgColors = rawPosData.map((d, i) => chartColors[(i + 3) % chartColors.length]);
|
||||
|
||||
new Chart(document.getElementById('posChart'), {
|
||||
type: 'doughnut',
|
||||
data: { labels: posLabels, datasets: [{ data: posCounts, backgroundColor: posBgColors, borderWidth: 2 }] },
|
||||
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, datalabels: { display: false } }, cutout: '65%' }
|
||||
});
|
||||
|
||||
const legendContainer = document.getElementById('posLegend');
|
||||
posLabels.forEach((label, index) => {
|
||||
const color = posBgColors[index];
|
||||
const count = posCounts[index];
|
||||
legendContainer.innerHTML += `<div class="flex items-center text-[11px] text-slate-600 bg-slate-50 px-2.5 py-1.5 rounded-lg border border-slate-200 shadow-sm shrink-0"><span class="w-3 h-3 rounded-full mr-2" style="background-color: ${color}"></span><span class="font-bold text-slate-800 mr-1">${count}</span> ${label}</div>`;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 38px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 38px; font-size: 0.875rem; color: #334155; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 36px; }
|
||||
</style>
|
||||
@endsection
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Laporan Data Karyawan</title>
|
||||
<style>
|
||||
body { font-family: Helvetica, Arial, sans-serif; font-size: 10px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { border: 1px solid #cbd5e1; padding: 6px; text-align: left; }
|
||||
th { background-color: #f8fafc; font-weight: bold; text-transform: uppercase; }
|
||||
.header { border-bottom: 2px solid #334155; padding-bottom: 10px; margin-bottom: 10px; }
|
||||
.header h2 { margin: 0 0 5px 0; font-size: 18px; color: #1e293b; }
|
||||
.header p { margin: 2px 0; font-size: 11px; color: #64748b; }
|
||||
.badge-active { color: #16a34a; font-weight: bold; }
|
||||
.badge-inactive { color: #dc2626; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h2>Laporan Data Karyawan</h2>
|
||||
<p><b>Total Data:</b> {{ $employees->count() }} Karyawan (Sesuai Filter)</p>
|
||||
<p><b>Dicetak pada:</b> {{ \Carbon\Carbon::now()->translatedFormat('d F Y - H:i:s') }}</p>
|
||||
<p><b>Dicetak oleh:</b> {{ auth()->user()->first_name }} {{ auth()->user()->last_name }}</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="3%">No</th>
|
||||
<th width="12%">NIK & Inisial</th>
|
||||
<th width="20%">Nama Lengkap</th>
|
||||
<th width="15%">Email</th>
|
||||
<th width="5%">L/P</th>
|
||||
<th width="15%">Departemen</th>
|
||||
<th width="15%">Jabatan</th>
|
||||
<th width="10%">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($employees as $index => $emp)
|
||||
<tr>
|
||||
<td style="text-align: center;">{{ $index + 1 }}</td>
|
||||
<td>{{ $emp->nik }} <br> <small>{{ strtoupper($emp->initial ?? '') }}</small></td>
|
||||
<td>{{ $emp->first_name }} {{ $emp->last_name }}</td>
|
||||
<td>{{ $emp->email }}</td>
|
||||
<td style="text-align: center;">{{ $emp->gender }}</td>
|
||||
<td>{{ $emp->department->name ?? '-' }}</td>
|
||||
<td>{{ $emp->position->name ?? '-' }}</td>
|
||||
<td>
|
||||
<span class="{{ $emp->is_active ? 'badge-active' : 'badge-inactive' }}">
|
||||
{{ $emp->is_active ? 'Aktif' : 'Non-Aktif' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,127 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
|
||||
<!-- HEADER & TOMBOL AKSI -->
|
||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="{{ route('admin.employees.index') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Profil Karyawan</h2>
|
||||
</div>
|
||||
<div class="flex space-x-3">
|
||||
<a href="{{ route('admin.employees.edit', $employee->id) }}" class="px-4 py-2 bg-white border border-slate-200 text-slate-700 rounded-lg text-sm font-semibold hover:bg-slate-50 flex items-center shadow-sm">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
||||
Edit Data
|
||||
</a>
|
||||
<form action="{{ route('admin.employees.destroy', $employee->id) }}" method="POST" onsubmit="return confirm('Apakah Anda yakin ingin menghapus karyawan ini secara permanen?');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="px-4 py-2 bg-red-50 text-red-600 border border-red-100 rounded-lg text-sm font-semibold hover:bg-red-100 flex items-center shadow-sm">
|
||||
Hapus
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KARTU PROFIL UTAMA -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden mb-6">
|
||||
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 h-24 sm:h-32"></div>
|
||||
<div class="px-6 sm:px-8 pb-8 relative">
|
||||
<div class="-mt-12 sm:-mt-16 flex justify-between items-end mb-4">
|
||||
<!-- Avatar Inisial -->
|
||||
<div class="w-24 h-24 sm:w-32 sm:h-32 bg-white rounded-full p-1.5 shadow-lg">
|
||||
<div class="w-full h-full bg-blue-100 rounded-full flex items-center justify-center text-blue-700 text-3xl font-bold border-2 border-blue-200">
|
||||
{{ strtoupper(substr($employee->first_name, 0, 1)) }}{{ $employee->last_name ? strtoupper(substr($employee->last_name, 0, 1)) : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Status Role Spatie -->
|
||||
<div class="mb-2">
|
||||
@foreach($employee->roles as $role)
|
||||
<span class="px-3 py-1 text-xs font-bold rounded-full bg-slate-900 text-white uppercase tracking-wider shadow-sm">
|
||||
Role: {{ $role->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 class="text-2xl font-extrabold text-slate-900">{{ $employee->first_name }} {{ $employee->last_name }}</h1>
|
||||
<p class="text-slate-500 font-medium">NIK: <span class="text-slate-800">{{ $employee->nik }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DETAIL INFORMASI -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
<!-- Kolom Kiri: Info Pekerjaan -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
|
||||
<h3 class="text-sm font-bold text-slate-800 uppercase tracking-wider mb-4 pb-2 border-b border-slate-100">Informasi Pekerjaan</h3>
|
||||
|
||||
<dl class="space-y-4">
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Departemen</dt>
|
||||
<dd class="text-sm font-bold text-indigo-700 mt-1">{{ $employee->department->name ?? 'Belum ditentukan' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Posisi / Jabatan</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->position->name ?? 'Belum ditentukan' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Tanggal Bergabung</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->join_date ? $employee->join_date->translatedFormat('d F Y') : '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Matrix Training Utama</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">
|
||||
@if($employee->training_matrix_id)
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-emerald-100 text-emerald-800">
|
||||
Matriks Tersedia (ID: {{ $employee->training_matrix_id }})
|
||||
</span>
|
||||
@else
|
||||
<span class="text-slate-400 italic">Belum dikaitkan ke matriks</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Kolom Kanan: Info Personal -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6">
|
||||
<h3 class="text-sm font-bold text-slate-800 uppercase tracking-wider mb-4 pb-2 border-b border-slate-100">Informasi Personal & Kontak</h3>
|
||||
|
||||
<dl class="space-y-4 grid grid-cols-2 gap-x-4">
|
||||
<div class="col-span-2">
|
||||
<dt class="text-xs font-semibold text-slate-500">Email Perusahaan</dt>
|
||||
<dd class="text-sm font-bold text-blue-600 mt-1">{{ $employee->email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">No. HP / Telepon</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->phone ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Nomor KTP</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->identity_number ?? '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Jenis Kelamin</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">
|
||||
{{ $employee->gender == 'L' ? 'Laki-laki' : ($employee->gender == 'P' ? 'Perempuan' : '-') }}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Tanggal Lahir</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1">{{ $employee->date_of_birth ? $employee->date_of_birth->translatedFormat('d F Y') : '-' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-xs font-semibold text-slate-500">Inisial Nama</dt>
|
||||
<dd class="text-sm font-bold text-slate-900 mt-1 uppercase">{{ $employee->initial ?? '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,145 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8" x-data="{ questionType: '{{ old('type', '') }}' }">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.exams.questions') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Tambah Soal Baru</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ url('admin/exams/questions') }}" method="POST" enctype="multipart/form-data" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Departemen</label>
|
||||
<select name="department_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Berlaku untuk Semua Departemen --</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id') == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan</label>
|
||||
<select name="position_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Berlaku untuk Semua Jabatan --</option>
|
||||
@foreach($positions as $pos)
|
||||
<option value="{{ $pos->id }}" {{ old('position_id') == $pos->id ? 'selected' : '' }}>{{ $pos->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Materi SOP / Modul <span class="text-red-500">*</span></label>
|
||||
<select name="training_matrix_id" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Materi Pembelajaran --</option>
|
||||
@foreach($matrices as $matrix)
|
||||
<option value="{{ $matrix->id }}" {{ old('training_matrix_id') == $matrix->id ? 'selected' : '' }}>{{ $matrix->title }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tipe Soal <span class="text-red-500">*</span></label>
|
||||
<select name="type" x-model="questionType" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Tipe Pertanyaan --</option>
|
||||
<option value="single">Single Choice (Satu Jawaban Benar)</option>
|
||||
<option value="multiple">Multiple Choice (Banyak Jawaban Benar)</option>
|
||||
<option value="true_false">True / False (Benar / Salah)</option>
|
||||
<option value="descriptive">Descriptive (Esai / Uraian)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tingkat Kesulitan <span class="text-red-500">*</span></label>
|
||||
<select name="level" class="select2 w-full text-sm" required>
|
||||
<option value="mudah" {{ old('level') == 'mudah' ? 'selected' : '' }}>Mudah (Easy)</option>
|
||||
<option value="sedang" {{ old('level') == 'sedang' ? 'selected' : '' }}>Sedang (Medium)</option>
|
||||
<option value="sulit" {{ old('level') == 'sulit' ? 'selected' : '' }}>Sulit (Hard)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Pertanyaan <span class="text-red-500">*</span></label>
|
||||
<textarea name="question_text" rows="4" required class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-blue-500/20 focus:border-blue-600 transition-all text-sm" placeholder="Tuliskan pertanyaan di sini...">{{ old('question_text') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6" x-show="questionType !== ''" x-transition>
|
||||
<h3 class="text-sm font-bold text-slate-800 uppercase tracking-wider mb-4 border-l-4 border-blue-500 pl-3">Pengaturan Jawaban</h3>
|
||||
|
||||
<div x-show="questionType === 'single'" style="display: none;" class="space-y-3">
|
||||
<p class="text-xs text-slate-500 mb-3">Pilih satu radio button sebagai jawaban yang benar.</p>
|
||||
@foreach(['A', 'B', 'C', 'D'] as $opt)
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="radio" name="correct_answer" value="{{ $opt }}" class="w-5 h-5 text-blue-600 border-slate-300 focus:ring-blue-500 cursor-pointer">
|
||||
<input type="text" name="options[{{ $opt }}]" placeholder="Opsi Jawaban {{ $opt }}" class="flex-1 px-4 py-2 bg-white border border-slate-200 rounded-lg text-sm focus:border-blue-500">
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div x-show="questionType === 'multiple'" style="display: none;" class="space-y-3">
|
||||
<p class="text-xs text-slate-500 mb-3">Centang lebih dari satu checkbox untuk jawaban yang benar.</p>
|
||||
@foreach(['A', 'B', 'C', 'D', 'E'] as $opt)
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" name="correct_answers[]" value="{{ $opt }}" class="w-5 h-5 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
|
||||
<input type="text" name="options[{{ $opt }}]" placeholder="Opsi Jawaban {{ $opt }}" class="flex-1 px-4 py-2 bg-white border border-slate-200 rounded-lg text-sm focus:border-blue-500">
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div x-show="questionType === 'true_false'" style="display: none;" class="flex gap-6">
|
||||
<label class="flex items-center gap-2 p-4 border border-slate-200 rounded-xl cursor-pointer hover:bg-slate-50 w-48">
|
||||
<input type="radio" name="correct_answer" value="true" class="w-5 h-5 text-emerald-600 border-slate-300 focus:ring-emerald-500">
|
||||
<span class="font-bold text-slate-700">True (Benar)</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 p-4 border border-slate-200 rounded-xl cursor-pointer hover:bg-slate-50 w-48">
|
||||
<input type="radio" name="correct_answer" value="false" class="w-5 h-5 text-red-600 border-slate-300 focus:ring-red-500">
|
||||
<span class="font-bold text-slate-700">False (Salah)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div x-show="questionType === 'descriptive'" style="display: none;">
|
||||
<p class="text-xs text-slate-500 mb-3">Tuliskan kata kunci / panduan jawaban benar untuk penilai (Opsional).</p>
|
||||
<textarea name="expected_answer" rows="3" class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:border-blue-500" placeholder="Kata kunci jawaban..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 flex justify-end space-x-3 border-t border-slate-100 mt-8">
|
||||
<a href="{{ route('admin.exams.questions') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-md transition-all">Simpan Soal</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
// Menjembatani Select2 dengan Alpine.js agar x-model ter-update saat Select2 berubah
|
||||
$('select[name="type"]').on('change', function() {
|
||||
// Ambil elemen div pembungkus Alpine (x-data) dan ubah nilainya
|
||||
let el = document.querySelector('[x-data]');
|
||||
if(el && el.__x) {
|
||||
el.__x.$data.questionType = $(this).val();
|
||||
} else {
|
||||
// Alternatif dispatch event custom jika pakai versi Alpine V3 terbaru
|
||||
el.dispatchEvent(new CustomEvent('update-type', { detail: $(this).val() }));
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,101 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 42px; border-radius: 0.75rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 42px; font-size: 0.875rem; color: #334155; padding-left: 1rem; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 40px; right: 10px; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-5xl mx-auto px-4 py-8" x-data="{ questionType: '{{ old('type', $question->type) }}' }" @update-type.window="questionType = $event.detail">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.exams.questions') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Soal: #{{ $question->id }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ url('admin/exams/questions/' . $question->id) }}" method="POST" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf @method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Departemen</label>
|
||||
<select name="department_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Semua Departemen --</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id', $question->department_id) == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Posisi / Jabatan</label>
|
||||
<select name="position_id" class="select2 w-full text-sm">
|
||||
<option value="">-- Semua Jabatan --</option>
|
||||
@foreach($positions as $pos)
|
||||
<option value="{{ $pos->id }}" {{ old('position_id', $question->position_id) == $pos->id ? 'selected' : '' }}>{{ $pos->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Materi SOP / Modul <span class="text-red-500">*</span></label>
|
||||
<select name="training_matrix_id" class="select2 w-full text-sm" required>
|
||||
<option value="">-- Pilih Materi Pembelajaran --</option>
|
||||
@foreach($matrices as $matrix)
|
||||
<option value="{{ $matrix->id }}" {{ old('training_matrix_id', $question->training_matrix_id) == $matrix->id ? 'selected' : '' }}>{{ $matrix->title }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tipe Soal <span class="text-red-500">*</span></label>
|
||||
<select name="type" class="select2 w-full text-sm" required>
|
||||
<option value="single" {{ $question->type == 'single' ? 'selected' : '' }}>Single Choice (Satu Jawaban Benar)</option>
|
||||
<option value="multiple" {{ $question->type == 'multiple' ? 'selected' : '' }}>Multiple Choice (Banyak Jawaban Benar)</option>
|
||||
<option value="true_false" {{ $question->type == 'true_false' ? 'selected' : '' }}>True / False (Benar / Salah)</option>
|
||||
<option value="descriptive" {{ $question->type == 'descriptive' ? 'selected' : '' }}>Descriptive (Esai / Uraian)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Tingkat Kesulitan <span class="text-red-500">*</span></label>
|
||||
<select name="level" class="select2 w-full text-sm" required>
|
||||
<option value="mudah" {{ $question->level == 'mudah' ? 'selected' : '' }}>Mudah (Easy)</option>
|
||||
<option value="sedang" {{ $question->level == 'sedang' ? 'selected' : '' }}>Sedang (Medium)</option>
|
||||
<option value="sulit" {{ $question->level == 'sulit' ? 'selected' : '' }}>Sulit (Hard)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 pt-6">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">Pertanyaan <span class="text-red-500">*</span></label>
|
||||
<textarea name="question_text" rows="4" required class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:border-blue-500">{{ old('question_text', $question->question_text) }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 border-l-4 border-amber-500 p-4 mt-4">
|
||||
<p class="text-sm text-amber-800 font-semibold">Tipe Soal diubah?</p>
|
||||
<p class="text-xs text-amber-700 mt-1">Jika Anda mengubah tipe soal, pastikan Anda mengisi kembali opsi jawabannya di bawah ini karena format jawabannya berubah.</p>
|
||||
</div>
|
||||
|
||||
<div class="pt-6 flex justify-end space-x-3 border-t border-slate-100 mt-8">
|
||||
<a href="{{ route('admin.exams.questions') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-700 rounded-xl shadow-md transition-all">Perbarui Soal</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2({ width: '100%' });
|
||||
|
||||
$('select[name="type"]').on('change', function() {
|
||||
window.dispatchEvent(new CustomEvent('update-type', { detail: $(this).val() }));
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,217 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
|
||||
<style>
|
||||
.select2-container .select2-selection--single { height: 38px; border-radius: 0.5rem; border-color: #e2e8f0; background-color: #f8fafc; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__rendered { line-height: 38px; font-size: 0.875rem; color: #334155; }
|
||||
.select2-container--default .select2-selection--single .select2-selection__arrow { height: 36px; }
|
||||
</style>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 py-8"
|
||||
x-data="{
|
||||
selectedQuestions: [],
|
||||
selectAll: false,
|
||||
toggleAll() {
|
||||
if (this.selectAll) {
|
||||
this.selectedQuestions = Array.from(document.querySelectorAll('.question-checkbox')).map(cb => cb.value);
|
||||
} else {
|
||||
this.selectedQuestions = [];
|
||||
}
|
||||
},
|
||||
confirmBulkDelete() {
|
||||
if(this.selectedQuestions.length === 0) {
|
||||
alert('Pilih minimal satu soal untuk dihapus!');
|
||||
return false;
|
||||
}
|
||||
return confirm('Yakin ingin menghapus ' + this.selectedQuestions.length + ' soal terpilih secara permanen?');
|
||||
}
|
||||
}">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-6 gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Bank Soal (Question Bank)</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Kelola dan filter total <span class="font-bold text-blue-600">{{ $totalQuestions }}</span> soal ujian/kuis.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<form action="{{ url('admin/exams/questions/bulk-delete') }}" method="POST" x-show="selectedQuestions.length > 0" x-transition @submit="return confirmBulkDelete()">
|
||||
@csrf @method('DELETE')
|
||||
<template x-for="id in selectedQuestions" :key="id">
|
||||
<input type="hidden" name="question_ids[]" :value="id">
|
||||
</template>
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-red-50 text-red-600 border border-red-200 rounded-lg text-sm font-bold hover:bg-red-100 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
||||
Bulk Delete (<span x-text="selectedQuestions.length"></span>)
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<a href="{{ url('admin/exams/questions/import') }}" class="inline-flex items-center px-4 py-2 bg-slate-800 text-white rounded-lg text-sm font-semibold hover:bg-slate-900 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path></svg>
|
||||
Import Soal
|
||||
</a>
|
||||
|
||||
<a href="{{ url('admin/exams/questions/create') }}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 shadow-sm transition-all">
|
||||
+ Tambah Soal
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm mb-6">
|
||||
<form action="{{ route('admin.exams.questions') }}" method="GET">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Departemen</label>
|
||||
<select name="department_id" class="select2 w-full text-sm">
|
||||
<option value="">Semua</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ request('department_id') == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Posisi</label>
|
||||
<select name="position_id" class="select2 w-full text-sm">
|
||||
<option value="">Semua</option>
|
||||
@foreach($positions as $pos)
|
||||
<option value="{{ $pos->id }}" {{ request('position_id') == $pos->id ? 'selected' : '' }}>{{ $pos->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Materi / Modul</label>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Tipe Soal</label>
|
||||
<select name="question_type" class="select2 w-full text-sm">
|
||||
<option value="">Semua</option>
|
||||
<option value="single" {{ request('question_type') == 'single' ? 'selected' : '' }}>Single Choice</option>
|
||||
<option value="multiple" {{ request('question_type') == 'multiple' ? 'selected' : '' }}>Multiple Choice</option>
|
||||
<option value="true_false" {{ request('question_type') == 'true_false' ? 'selected' : '' }}>True / False</option>
|
||||
<option value="descriptive" {{ request('question_type') == 'descriptive' ? 'selected' : '' }}>Deskriptif</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Level</label>
|
||||
<select name="question_level" class="select2 w-full text-sm">
|
||||
<option value="">Semua</option>
|
||||
<option value="mudah" {{ request('question_level') == 'mudah' ? 'selected' : '' }}>Mudah (Easy)</option>
|
||||
<option value="sedang" {{ request('question_level') == 'sedang' ? 'selected' : '' }}>Sedang (Medium)</option>
|
||||
<option value="sulit" {{ request('question_level') == 'sulit' ? 'selected' : '' }}>Sulit (Hard)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-500 mb-1">Pembuat Soal</label>
|
||||
<select name="created_by" class="select2 w-full text-sm">
|
||||
<option value="">Semua</option>
|
||||
@foreach($creators as $creator)
|
||||
<option value="{{ $creator->id }}" {{ request('created_by') == $creator->id ? 'selected' : '' }}>{{ $creator->first_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4 items-center justify-between border-t border-slate-100 pt-4">
|
||||
<div class="w-full md:w-1/3">
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="Cari teks pertanyaan..." class="w-full px-4 py-2 bg-slate-50 border border-slate-200 rounded-lg text-sm focus:ring-blue-500">
|
||||
</div>
|
||||
<div class="flex gap-2 w-full md:w-auto">
|
||||
@if(request()->hasAny(['search', 'department_id', 'position_id', 'question_type', 'question_level', 'created_by']))
|
||||
<a href="{{ route('admin.exams.questions') }}" class="px-5 py-2 bg-slate-100 text-slate-600 rounded-lg text-sm font-semibold hover:bg-slate-200 transition-all text-center w-full md:w-auto">Reset</a>
|
||||
@endif
|
||||
<button type="submit" class="px-5 py-2 bg-blue-600 text-white rounded-lg text-sm font-semibold hover:bg-blue-700 transition-all text-center w-full md:w-auto">
|
||||
Terapkan Filter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-2xl overflow-hidden shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="bg-slate-50 text-slate-600 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th class="px-4 py-4 w-10 text-center">
|
||||
<input type="checkbox" x-model="selectAll" @change="toggleAll" class="w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
|
||||
</th>
|
||||
<th class="px-4 py-4 font-semibold">Q.ID</th>
|
||||
<th class="px-4 py-4 font-semibold">Materi / Modul</th>
|
||||
<th class="px-4 py-4 font-semibold">Tipe & Level</th>
|
||||
<th class="px-4 py-4 font-semibold w-1/3">Pertanyaan</th>
|
||||
<th class="px-4 py-4 font-semibold">Pembuat</th>
|
||||
<th class="px-4 py-4 font-semibold text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($questions as $q)
|
||||
<tr class="hover:bg-slate-50 transition-colors" :class="selectedQuestions.includes('{{ $q->id }}') ? 'bg-blue-50/50' : ''">
|
||||
<td class="px-4 py-3 text-center">
|
||||
<input type="checkbox" value="{{ $q->id }}" x-model="selectedQuestions" class="question-checkbox w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500 cursor-pointer">
|
||||
</td>
|
||||
<td class="px-4 py-3 font-mono font-bold text-slate-500">{{ $q->id }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="font-semibold text-slate-800">{{ $q->matrix->title ?? 'Umum' }}</span>
|
||||
<div class="text-[11px] text-slate-500 mt-0.5">{{ $q->department->name ?? 'All Dept' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="px-2 py-0.5 bg-indigo-50 text-indigo-700 rounded text-[10px] font-bold uppercase tracking-wider border border-indigo-100">{{ str_replace('_', ' ', $q->type) }}</span>
|
||||
<div class="mt-1">
|
||||
@if($q->level == 'mudah') <span class="text-[11px] font-bold text-emerald-500">Mudah</span>
|
||||
@elseif($q->level == 'sedang') <span class="text-[11px] font-bold text-amber-500">Sedang</span>
|
||||
@else <span class="text-[11px] font-bold text-red-500">Sulit</span> @endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<p class="text-sm text-slate-800 line-clamp-2">{{ strip_tags($q->question_text) }}</p>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="text-sm font-semibold">{{ $q->creator->first_name ?? 'Sistem' }}</div>
|
||||
<div class="text-[11px] text-slate-400">@if($q->creator && $q->creator->hasRole('trainer')) Trainer @else Admin @endif</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right space-x-2 whitespace-nowrap">
|
||||
<a href="#" class="text-slate-400 hover:text-blue-600"><svg class="w-5 h-5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg></a>
|
||||
<a href="#" class="text-slate-400 hover:text-amber-500"><svg class="w-5 h-5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg></a>
|
||||
<form action="#" method="POST" class="inline-block" onsubmit="return confirm('Hapus soal ini?');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="text-slate-400 hover:text-red-500"><svg class="w-5 h-5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-4 py-12 text-center">
|
||||
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path></svg>
|
||||
<p class="text-slate-500 font-medium">Belum ada data soal yang sesuai filter.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($questions->hasPages())
|
||||
<div class="p-4 border-t border-slate-100 bg-slate-50/50">
|
||||
{{ $questions->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Inisialisasi Select2 untuk semua filter
|
||||
$('.select2').select2({ width: '100%' });
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,74 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="{{ route('admin.exams.questions') }}" class="text-slate-400 hover:text-blue-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Detail Pertanyaan</h2>
|
||||
</div>
|
||||
<a href="{{ url('admin/exams/questions/'.$question->id.'/edit') }}" class="px-4 py-2 bg-blue-50 text-blue-700 rounded-lg text-sm font-bold hover:bg-blue-100 transition-colors">
|
||||
Edit Soal
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 mb-6">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<span class="block text-xs font-semibold text-slate-400 uppercase">Q.ID</span>
|
||||
<span class="block text-sm font-mono font-bold text-slate-900">#{{ $question->id }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-xs font-semibold text-slate-400 uppercase">Materi SOP</span>
|
||||
<span class="block text-sm font-medium text-slate-900">{{ $question->matrix->title ?? 'Umum' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-xs font-semibold text-slate-400 uppercase">Tipe Soal</span>
|
||||
<span class="inline-block mt-1 px-2 py-0.5 bg-indigo-50 text-indigo-700 rounded text-[10px] font-bold uppercase border border-indigo-100">
|
||||
{{ str_replace('_', ' ', $question->type) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-xs font-semibold text-slate-400 uppercase">Level</span>
|
||||
<span class="block text-sm font-bold mt-1 {{ $question->level == 'mudah' ? 'text-emerald-500' : ($question->level == 'sedang' ? 'text-amber-500' : 'text-red-500') }}">
|
||||
{{ strtoupper($question->level) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-100 mt-4 pt-4 flex gap-6">
|
||||
<div>
|
||||
<span class="block text-xs font-semibold text-slate-400 uppercase">Dikhususkan untuk Departemen:</span>
|
||||
<span class="block text-sm text-slate-700">{{ $question->department->name ?? 'Semua Departemen' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="block text-xs font-semibold text-slate-400 uppercase">Dikhususkan untuk Jabatan:</span>
|
||||
<span class="block text-sm text-slate-700">{{ $question->position->name ?? 'Semua Jabatan' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-6 sm:p-8">
|
||||
<h4 class="text-sm font-bold text-slate-400 uppercase tracking-wider mb-4 border-b border-slate-100 pb-2">Isi Pertanyaan</h4>
|
||||
|
||||
<div class="prose prose-slate max-w-none text-slate-800 text-lg mb-8">
|
||||
{!! nl2br(e($question->question_text)) !!}
|
||||
</div>
|
||||
|
||||
<h4 class="text-sm font-bold text-slate-400 uppercase tracking-wider mb-4 border-b border-slate-100 pb-2">Opsi Jawaban & Kunci</h4>
|
||||
|
||||
<div class="space-y-3">
|
||||
@if($question->type === 'descriptive')
|
||||
<div class="p-4 bg-slate-50 border border-slate-200 rounded-xl">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase">Kunci / Panduan Penilaian:</span>
|
||||
<p class="text-slate-800 mt-2">{{ $question->expected_answer ?? 'Tidak ada kata kunci khusus yang diatur.' }}</p>
|
||||
</div>
|
||||
@else
|
||||
<p class="italic text-sm text-slate-500">Tampilan opsi jawaban disesuaikan dengan struktur relasi database opsi Anda.</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,96 @@
|
||||
@extends('layouts.app') @section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Manajemen Master Data</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Kelola data Departemen dan Posisi (Jabatan) untuk struktur organisasi perusahaan.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden flex flex-col">
|
||||
<div class="p-5 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
|
||||
<h3 class="text-lg font-bold text-slate-800 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
|
||||
Daftar Departemen
|
||||
</h3>
|
||||
<button class="bg-indigo-50 text-indigo-600 hover:bg-indigo-100 px-3 py-1.5 rounded-lg text-sm font-semibold transition-colors">
|
||||
+ Tambah Baru
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 p-0 overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider">
|
||||
<tr>
|
||||
<th class="px-5 py-3 font-semibold">ID</th>
|
||||
<th class="px-5 py-3 font-semibold">Nama Departemen</th>
|
||||
<th class="px-5 py-3 font-semibold text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td class="px-5 py-4 font-medium text-slate-900">1</td>
|
||||
<td class="px-5 py-4">Quality Assurance (QA)</td>
|
||||
<td class="px-5 py-4 text-right">
|
||||
<button class="text-indigo-600 hover:text-indigo-800 font-medium text-sm mr-3">Edit</button>
|
||||
<button class="text-red-500 hover:text-red-700 font-medium text-sm">Hapus</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td class="px-5 py-4 font-medium text-slate-900">2</td>
|
||||
<td class="px-5 py-4">Produksi Farmasi</td>
|
||||
<td class="px-5 py-4 text-right">
|
||||
<button class="text-indigo-600 hover:text-indigo-800 font-medium text-sm mr-3">Edit</button>
|
||||
<button class="text-red-500 hover:text-red-700 font-medium text-sm">Hapus</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden flex flex-col">
|
||||
<div class="p-5 border-b border-slate-100 flex justify-between items-center bg-slate-50/50">
|
||||
<h3 class="text-lg font-bold text-slate-800 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
|
||||
Daftar Posisi
|
||||
</h3>
|
||||
<button class="bg-emerald-50 text-emerald-600 hover:bg-emerald-100 px-3 py-1.5 rounded-lg text-sm font-semibold transition-colors">
|
||||
+ Tambah Baru
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1 p-0 overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider">
|
||||
<tr>
|
||||
<th class="px-5 py-3 font-semibold">ID</th>
|
||||
<th class="px-5 py-3 font-semibold">Nama Posisi</th>
|
||||
<th class="px-5 py-3 font-semibold text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td class="px-5 py-4 font-medium text-slate-900">1</td>
|
||||
<td class="px-5 py-4">Manager</td>
|
||||
<td class="px-5 py-4 text-right">
|
||||
<button class="text-indigo-600 hover:text-indigo-800 font-medium text-sm mr-3">Edit</button>
|
||||
<button class="text-red-500 hover:text-red-700 font-medium text-sm">Hapus</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td class="px-5 py-4 font-medium text-slate-900">2</td>
|
||||
<td class="px-5 py-4">Supervisor QA</td>
|
||||
<td class="px-5 py-4 text-right">
|
||||
<button class="text-indigo-600 hover:text-indigo-800 font-medium text-sm mr-3">Edit</button>
|
||||
<button class="text-red-500 hover:text-red-700 font-medium text-sm">Hapus</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,41 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-3xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.positions.index') }}" class="text-slate-400 hover:text-emerald-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Tambah Posisi/Jabatan Baru</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ route('admin.positions.store') }}" method="POST" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="department_id" class="block text-sm font-bold text-slate-700 mb-2">Departemen Induk <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="department_id" required class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-600 transition-all">
|
||||
<option value="">-- Pilih Departemen --</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id') == $dept->id ? 'selected' : '' }}>{{ $dept->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('department_id') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-bold text-slate-700 mb-2">Nama Posisi <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name') }}" required placeholder="Contoh: Manager Produksi"
|
||||
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-600 transition-all">
|
||||
@error('name') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="pt-4 flex justify-end space-x-3 border-t border-slate-100">
|
||||
<a href="{{ route('admin.positions.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl shadow-md transition-all">Simpan Posisi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,42 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-3xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center space-x-3">
|
||||
<a href="{{ route('admin.positions.index') }}" class="text-slate-400 hover:text-emerald-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Edit Posisi / Jabatan</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<form action="{{ route('admin.positions.update', $position->id) }}" method="POST" class="p-6 sm:p-8 space-y-6">
|
||||
@csrf
|
||||
@method('PUT') <div>
|
||||
<label for="department_id" class="block text-sm font-bold text-slate-700 mb-2">Departemen Induk <span class="text-red-500">*</span></label>
|
||||
<select name="department_id" id="department_id" required class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-600 transition-all">
|
||||
<option value="">-- Pilih Departemen --</option>
|
||||
@foreach($departments as $dept)
|
||||
<option value="{{ $dept->id }}" {{ old('department_id', $position->department_id) == $dept->id ? 'selected' : '' }}>
|
||||
{{ $dept->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('department_id') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-bold text-slate-700 mb-2">Nama Posisi <span class="text-red-500">*</span></label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name', $position->name) }}" required placeholder="Contoh: Manager Produksi"
|
||||
class="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:ring-2 focus:ring-emerald-500/20 focus:border-emerald-600 transition-all">
|
||||
@error('name') <span class="text-xs text-red-500 mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="pt-4 flex justify-end space-x-3 border-t border-slate-100">
|
||||
<a href="{{ route('admin.positions.index') }}" class="px-5 py-2.5 text-sm font-semibold text-slate-600 hover:bg-slate-100 rounded-xl transition-colors">Batal</a>
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-semibold text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl shadow-md transition-all">Perbarui Posisi</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Master Data Jabatan</title>
|
||||
<style>
|
||||
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { border: 1px solid #cbd5e1; padding: 8px; text-align: left; }
|
||||
th { background-color: #f8fafc; font-weight: bold; text-transform: uppercase; }
|
||||
.header { border-bottom: 2px solid #334155; padding-bottom: 10px; margin-bottom: 10px; }
|
||||
.header h2 { margin: 0 0 5px 0; font-size: 18px; color: #1e293b; }
|
||||
.header p { margin: 2px 0; font-size: 12px; color: #64748b; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<h2>Laporan Master Data Jabatan</h2>
|
||||
<p><b>Total Data:</b> {{ $positions->count() }} Jabatan</p>
|
||||
<p><b>Dicetak pada:</b> {{ \Carbon\Carbon::now()->translatedFormat('d F Y - H:i:s') }}</p>
|
||||
<p><b>Dicetak oleh:</b> {{ auth()->user()->first_name }} {{ auth()->user()->last_name }}</p>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%" style="text-align: center;">No</th>
|
||||
<th width="60%">Nama Jabatan / Posisi</th>
|
||||
<th width="30%">Tanggal Dibuat</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($positions as $index => $pos)
|
||||
<tr>
|
||||
<td style="text-align: center;">{{ $index + 1 }}</td>
|
||||
<td>{{ $pos->name }}</td>
|
||||
<td>{{ $pos->created_at ? \Carbon\Carbon::parse($pos->created_at)->format('d-m-Y H:i') : '-' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"
|
||||
x-data="{
|
||||
showCols: { id: true, name: true, action: true },
|
||||
exportOpen: false,
|
||||
columnOpen: false
|
||||
}">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Manajemen Posisi / Jabatan</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Total <span class="font-bold text-indigo-600">{{ $positions instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator ? $positions->total() : $positions->count() }}</span> rekor ditemukan.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.positions.create') }}" class="inline-flex items-center justify-center px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-semibold hover:bg-indigo-700 shadow-sm transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||||
Tambah Posisi
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-4 rounded-t-2xl border border-slate-200 border-b-0 flex flex-col md:flex-row gap-4 justify-between items-center relative z-10">
|
||||
|
||||
<form action="{{ route('admin.positions.index') }}" method="GET" class="w-full md:w-96 relative">
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="Cari nama jabatan..."
|
||||
class="w-full pl-10 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-600 transition-all">
|
||||
<svg class="w-4 h-4 text-slate-400 absolute left-3.5 top-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
@if(request('search'))
|
||||
<a href="{{ route('admin.positions.index') }}" class="absolute right-3 top-2.5 text-xs text-red-500 hover:underline">Clear</a>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<div class="flex items-center space-x-3 w-full md:w-auto">
|
||||
<div class="relative">
|
||||
<button @click="columnOpen = !columnOpen" @click.away="columnOpen = false" class="px-4 py-2 bg-white border border-slate-200 text-slate-700 rounded-xl text-sm font-semibold hover:bg-slate-50 flex items-center shadow-sm">
|
||||
<svg class="w-4 h-4 mr-2 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2"></path></svg>
|
||||
Kolom
|
||||
</button>
|
||||
<div x-show="columnOpen" x-transition class="absolute right-0 mt-2 w-48 bg-white border border-slate-100 rounded-xl shadow-xl py-2 z-50" style="display: none;">
|
||||
<label class="flex items-center px-4 py-2 hover:bg-slate-50 cursor-pointer text-sm"><input type="checkbox" x-model="showCols.id" class="rounded border-slate-300 text-indigo-600 mr-3"> ID</label>
|
||||
<label class="flex items-center px-4 py-2 hover:bg-slate-50 cursor-pointer text-sm"><input type="checkbox" x-model="showCols.name" class="rounded border-slate-300 text-indigo-600 mr-3"> Nama Posisi</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<button @click="exportOpen = !exportOpen" @click.away="exportOpen = false" class="px-4 py-2 bg-emerald-50 border border-emerald-200 text-emerald-700 rounded-xl text-sm font-semibold hover:bg-emerald-100 flex items-center transition-all">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>
|
||||
Export Data
|
||||
</button>
|
||||
<div x-show="exportOpen" x-transition class="absolute right-0 mt-2 w-48 bg-white border border-slate-100 rounded-xl shadow-xl py-2 z-50" style="display: none;">
|
||||
<p class="px-4 py-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider">Pilih Format</p>
|
||||
<a href="{{ url('admin/positions/export/excel?search=' . request('search')) }}" class="flex items-center px-4 py-2 hover:bg-slate-50 text-sm text-slate-700">
|
||||
<span class="w-2 h-2 rounded-full bg-emerald-500 mr-2"></span> Excel (.xlsx)
|
||||
</a>
|
||||
<a href="{{ url('admin/positions/export/pdf?search=' . request('search')) }}" class="flex items-center px-4 py-2 hover:bg-slate-50 text-sm text-slate-700">
|
||||
<span class="w-2 h-2 rounded-full bg-red-500 mr-2"></span> PDF (.pdf)
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white border border-slate-200 rounded-b-2xl overflow-hidden shadow-sm relative z-0">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50/80 text-slate-500 text-xs uppercase tracking-wider border-b border-slate-200">
|
||||
<tr>
|
||||
<th x-show="showCols.id" class="px-6 py-4 font-semibold">ID</th>
|
||||
<th x-show="showCols.name" class="px-6 py-4 font-semibold">Nama Posisi</th>
|
||||
<th x-show="showCols.action" class="px-6 py-4 font-semibold text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($positions as $pos)
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td x-show="showCols.id" class="px-6 py-4 font-medium text-slate-500">#{{ $pos->id }}</td>
|
||||
<td x-show="showCols.name" class="px-6 py-4 font-bold text-slate-900">{{ $pos->name }}</td>
|
||||
<td x-show="showCols.action" class="px-6 py-4 text-right space-x-3">
|
||||
<a href="{{ route('admin.positions.show', $pos->id) }}" class="text-slate-400 hover:text-indigo-600 font-medium text-sm transition-colors">Detail</a>
|
||||
<a href="{{ route('admin.positions.edit', $pos->id) }}" class="text-indigo-600 hover:text-indigo-800 font-medium text-sm transition-colors">Edit</a>
|
||||
|
||||
<form action="{{ route('admin.positions.destroy', $pos->id) }}" method="POST" class="inline-block" onsubmit="return confirm('Apakah Anda yakin ingin menghapus posisi ini?');">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="text-red-500 hover:text-red-700 font-medium text-sm transition-colors">Hapus</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="px-6 py-12 text-center text-slate-500">
|
||||
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"></path></svg>
|
||||
Tidak ada data posisi yang ditemukan.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(method_exists($positions, 'hasPages') && $positions->hasPages())
|
||||
<div class="p-4 border-t border-slate-100 bg-slate-50/50">
|
||||
{{ $positions->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,56 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="{{ route('admin.positions.index') }}" class="text-slate-400 hover:text-emerald-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Detail Posisi / Jabatan</h2>
|
||||
</div>
|
||||
<a href="{{ route('admin.positions.edit', $position->id) }}" class="px-4 py-2 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 rounded-lg text-sm font-bold transition-all">
|
||||
Edit Data
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 p-8 mb-8 relative overflow-hidden">
|
||||
<div class="absolute top-0 right-0 w-32 h-32 bg-emerald-50 rounded-bl-full -mr-8 -mt-8 opacity-50 pointer-events-none"></div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-start justify-between relative z-10">
|
||||
<div class="mb-4 sm:mb-0">
|
||||
<div class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800 mb-3 border border-indigo-200">
|
||||
Departemen: {{ $position->department->name ?? 'Tidak Ada Departemen Induk' }}
|
||||
</div>
|
||||
<h1 class="text-3xl font-extrabold text-slate-900">{{ $position->name }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="bg-emerald-100 text-emerald-700 px-5 py-3 rounded-xl text-center">
|
||||
<span class="block text-2xl font-bold">0</span>
|
||||
<span class="block text-[10px] uppercase tracking-wider font-bold mt-1 opacity-80">Karyawan</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-bold text-slate-800 mb-4">Daftar Karyawan dengan Jabatan Ini</h3>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="bg-slate-50 text-slate-500">
|
||||
<tr>
|
||||
<th class="p-4 font-semibold">Nama Karyawan</th>
|
||||
<th class="p-4 font-semibold">NIK</th>
|
||||
<th class="p-4 font-semibold text-right">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
<tr>
|
||||
<td colspan="3" class="p-8 text-center text-slate-500 italic">
|
||||
<svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
|
||||
Belum ada karyawan yang terdaftar dengan jabatan ini.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,84 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<a href="{{ route('admin.reports.training') }}" class="text-slate-400 hover:text-indigo-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Rapor Evaluasi Karyawan</h2>
|
||||
</div>
|
||||
<button class="px-4 py-2 bg-slate-900 text-white rounded-lg text-sm font-semibold shadow-md hover:bg-slate-800 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"></path></svg>
|
||||
Cetak PDF
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
|
||||
<p class="text-sm font-semibold text-slate-500 mb-1">Identitas Trainee</p>
|
||||
<h3 class="text-xl font-extrabold text-slate-900">{{ $user->first_name }} {{ $user->last_name }}</h3>
|
||||
<p class="text-xs text-slate-400 mt-1">NIK: {{ $user->nik ?? 'N/A' }}</p>
|
||||
</div>
|
||||
<div class="bg-indigo-50 p-6 rounded-2xl border border-indigo-100">
|
||||
<p class="text-sm font-semibold text-indigo-600 mb-1">Total Evaluasi</p>
|
||||
<h3 class="text-3xl font-extrabold text-indigo-900">{{ $stats['total_exams'] }}</h3>
|
||||
</div>
|
||||
<div class="bg-emerald-50 p-6 rounded-2xl border border-emerald-100">
|
||||
<p class="text-sm font-semibold text-emerald-600 mb-1">Total Lulus</p>
|
||||
<h3 class="text-3xl font-extrabold text-emerald-900">{{ $stats['passed'] }}</h3>
|
||||
</div>
|
||||
<div class="bg-orange-50 p-6 rounded-2xl border border-orange-100">
|
||||
<p class="text-sm font-semibold text-orange-600 mb-1">Rata-rata Nilai</p>
|
||||
<h3 class="text-3xl font-extrabold text-orange-900">{{ number_format($stats['avg_score'], 1) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="p-6 border-b border-slate-100">
|
||||
<h3 class="text-lg font-bold text-slate-800">Riwayat Sesi Ujian</h3>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider">
|
||||
<tr>
|
||||
<th class="px-6 py-4 font-semibold">Tanggal Ujian</th>
|
||||
<th class="px-6 py-4 font-semibold">ID / Sesi Ujian</th>
|
||||
<th class="px-6 py-4 font-semibold text-center">Skor Akhir</th>
|
||||
<th class="px-6 py-4 font-semibold text-center">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($examHistory as $history)
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td class="px-6 py-4 text-slate-500 font-medium">
|
||||
{{ $history->created_at->format('d M Y - H:i') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 font-bold text-slate-900">
|
||||
Exam ID: #{{ $history->exam_id }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center font-extrabold {{ $history->score >= 70 ? 'text-emerald-600' : 'text-red-500' }}">
|
||||
{{ $history->score }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
@if($history->is_passed)
|
||||
<span class="px-3 py-1 text-xs font-bold bg-emerald-100 text-emerald-700 rounded-full">LULUS</span>
|
||||
@else
|
||||
<span class="px-3 py-1 text-xs font-bold bg-red-100 text-red-700 rounded-full">GAGAL</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-6 py-8 text-center text-slate-500">
|
||||
Karyawan ini belum pernah mengikuti evaluasi ujian.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,67 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Laporan Pelatihan Karyawan</h2>
|
||||
<p class="text-sm text-slate-500 mt-1">Pantau progres evaluasi CPOB dan kelulusan ujian setiap karyawan secara realtime.</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm whitespace-nowrap">
|
||||
<thead class="bg-slate-50 text-slate-500 text-xs uppercase tracking-wider">
|
||||
<tr>
|
||||
<th class="px-6 py-4 font-semibold">Nama Karyawan</th>
|
||||
<th class="px-6 py-4 font-semibold text-center">Total Ujian</th>
|
||||
<th class="px-6 py-4 font-semibold text-center">Lulus</th>
|
||||
<th class="px-6 py-4 font-semibold text-center">Rata-rata Nilai</th>
|
||||
<th class="px-6 py-4 font-semibold text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-slate-700">
|
||||
@forelse($trainees as $trainee)
|
||||
@php
|
||||
$totalExams = $trainee->examResults->count();
|
||||
$passed = $trainee->examResults->where('is_passed', true)->count();
|
||||
$avgScore = $totalExams > 0 ? $trainee->examResults->avg('score') : 0;
|
||||
@endphp
|
||||
<tr class="hover:bg-slate-50 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-bold text-slate-900">{{ $trainee->first_name }} {{ $trainee->last_name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ $trainee->email }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center font-medium">{{ $totalExams }}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<span class="px-2.5 py-1 text-xs font-bold rounded-full {{ $passed > 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-100 text-slate-500' }}">
|
||||
{{ $passed }} Modul
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center font-bold {{ $avgScore >= 70 ? 'text-emerald-600' : 'text-red-500' }}">
|
||||
{{ number_format($avgScore, 1) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<a href="{{ route('admin.reports.training.show', $trainee->id) }}" class="inline-flex items-center text-sm font-semibold text-indigo-600 hover:text-indigo-800 transition-colors">
|
||||
Lihat Detail
|
||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-12 text-center text-slate-500">
|
||||
Belum ada data evaluasi karyawan yang dapat ditampilkan.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if($trainees->hasPages())
|
||||
<div class="p-4 border-t border-slate-100">
|
||||
{{ $trainees->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,57 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Master SOP - HRIS')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800 tracking-tight">Dokumen SOP & CPOB</h2>
|
||||
<p class="text-sm text-slate-500">Pusat data prosedur operasi standar perusahaan.</p>
|
||||
</div>
|
||||
<a href="{{ route('admin.sops.create') }}" class="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm font-semibold hover:bg-indigo-700 shadow-sm transition-all">
|
||||
+ Registrasi SOP
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-slate-50 border-b border-slate-200 text-xs uppercase tracking-wider text-slate-500 font-bold">
|
||||
<th class="p-4">Kode SOP</th>
|
||||
<th class="p-4">Judul Dokumen</th>
|
||||
<th class="p-4">Versi</th>
|
||||
<th class="p-4">Status</th>
|
||||
<th class="p-4 text-right">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 text-sm text-slate-700">
|
||||
@foreach($sops as $sop)
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="p-4 font-mono font-medium">{{ $sop->sop_code }}</td>
|
||||
<td class="p-4 font-bold text-slate-800">{{ $sop->title }}</td>
|
||||
<td class="p-4">v{{ $sop->version }}</td>
|
||||
<td class="p-4">
|
||||
@if($sop->status == 'Active')
|
||||
<span class="px-2.5 py-1 bg-emerald-100 text-emerald-700 text-xs font-bold rounded-full">Active</span>
|
||||
@elseif($sop->status == 'Draft')
|
||||
<span class="px-2.5 py-1 bg-amber-100 text-amber-700 text-xs font-bold rounded-full">Draft</span>
|
||||
@else
|
||||
<span class="px-2.5 py-1 bg-slate-200 text-slate-700 text-xs font-bold rounded-full">Obsolete</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="p-4 text-right">
|
||||
<a href="#" class="text-slate-500 hover:text-indigo-600 font-medium text-xs">Lihat Detail</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-4 border-t border-slate-100">
|
||||
{{ $sops->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,69 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Portal Ujian Karyawan')
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto py-8">
|
||||
|
||||
<div class="bg-indigo-700 rounded-2xl shadow-lg p-8 text-white mb-8 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')]">
|
||||
<h1 class="text-3xl font-extrabold mb-2">Selamat Datang, {{ $user->first_name }}!</h1>
|
||||
<p class="text-indigo-200">Pastikan Anda menyelesaikan kualifikasi CPOB tepat waktu. Semangat!</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"></path></svg>
|
||||
Ujian & Kualifikasi Aktif
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
@forelse($availableExams as $exam)
|
||||
<div class="p-4 border border-slate-100 rounded-lg bg-slate-50 hover:bg-white hover:border-indigo-200 transition-all">
|
||||
<h4 class="font-bold text-slate-800 text-md">{{ $exam->title }}</h4>
|
||||
<div class="flex items-center text-xs text-slate-500 mt-2 mb-4 space-x-4">
|
||||
<span>Durasi: {{ $exam->duration }} Menit</span>
|
||||
<span>KKM: {{ $exam->passing_percentage }}</span>
|
||||
</div>
|
||||
<a href="#" class="inline-block w-full text-center px-4 py-2 bg-indigo-600 text-white rounded-md text-sm font-semibold hover:bg-indigo-700">
|
||||
Mulai Ujian
|
||||
</a>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-slate-500 text-center py-4">Belum ada ujian yang ditugaskan untuk Anda.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 p-6">
|
||||
<h3 class="text-lg font-bold text-slate-800 mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
Riwayat Kelulusan
|
||||
</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
@forelse($examHistory as $history)
|
||||
<div class="flex items-center justify-between p-3 border-b border-slate-100 last:border-0">
|
||||
<div>
|
||||
<p class="font-bold text-sm text-slate-800">{{ $history->exam->title }}</p>
|
||||
<p class="text-xs text-slate-400">{{ $history->created_at->format('d M Y') }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="block font-mono font-bold {{ $history->is_passed ? 'text-emerald-600' : 'text-red-600' }}">
|
||||
Skor: {{ $history->score }}
|
||||
</span>
|
||||
<span class="text-[10px] uppercase font-bold px-2 py-0.5 rounded-full {{ $history->is_passed ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700' }}">
|
||||
{{ $history->is_passed ? 'Lulus' : 'Gagal' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-slate-500 text-center py-4">Anda belum mengikuti ujian apapun.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Ujian CBT - {{ $exam->title }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="bg-slate-100 font-sans antialiased selection:bg-indigo-500 selection:text-white">
|
||||
|
||||
<div class="bg-white border-b border-slate-200 px-6 py-3 flex justify-between items-center shadow-sm">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-8 h-8 bg-indigo-600 rounded-md flex items-center justify-center text-white font-bold">
|
||||
T
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-sm font-bold text-slate-800 tracking-tight">Tunggal Pharma LMS</h1>
|
||||
<p class="text-[10px] text-slate-500 uppercase tracking-wider">Computer Based Test V2.0</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="text-sm font-bold text-slate-700">{{ Auth::user()->first_name }} {{ Auth::user()->last_name }}</span>
|
||||
<span class="text-xs px-2 py-0.5 bg-slate-100 text-slate-600 rounded border border-slate-200 ml-2 font-mono">
|
||||
{{ Auth::user()->nik }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="py-8 px-4 sm:px-6 lg:px-8">
|
||||
@livewire('cbt.exam-engine', ['exam' => $exam])
|
||||
</main>
|
||||
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<footer class="bg-white border-t border-slate-200 mt-auto py-6 relative z-10">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="md:flex md:items-center md:justify-between">
|
||||
|
||||
<div class="flex justify-center md:justify-end mb-4 md:mb-0 space-x-6 md:order-2">
|
||||
<a href="#" class="text-slate-400 hover:text-indigo-600 transition-colors">
|
||||
<span class="text-sm font-medium">Panduan Penggunaan</span>
|
||||
</a>
|
||||
<a href="#" class="text-slate-400 hover:text-indigo-600 transition-colors">
|
||||
<span class="text-sm font-medium">Tim IT Support</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 md:mt-0 md:order-1 flex items-center justify-center md:justify-start">
|
||||
<div class="w-6 h-6 bg-indigo-600 rounded flex items-center justify-center text-white font-bold text-xs mr-3">
|
||||
T
|
||||
</div>
|
||||
<p class="text-center md:text-left text-sm text-slate-500">
|
||||
© {{ date('Y') }} <span class="font-bold text-slate-700">Tunggal Pharma LMS</span>. Hak Cipta Dilindungi.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,40 @@
|
||||
<header class="bg-white border-b border-slate-200 sticky top-0 z-30">
|
||||
<div class="flex items-center justify-between px-6 py-3">
|
||||
|
||||
<div class="flex items-center">
|
||||
<button class="text-slate-500 hover:text-indigo-600 focus:outline-none lg:hidden mr-4">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
|
||||
</button>
|
||||
|
||||
<div class="hidden md:flex relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<svg class="w-5 h-5 text-slate-400" viewBox="0 0 24 24" fill="none"><path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
</span>
|
||||
<input type="text" class="w-64 py-2 pl-10 pr-4 text-sm text-slate-700 bg-slate-50 border border-slate-200 rounded-md focus:bg-white focus:border-indigo-500 focus:ring-indigo-500" placeholder="Cari modul atau SOP...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="relative flex items-center space-x-3">
|
||||
<div class="text-right hidden md:block">
|
||||
<p class="text-sm font-bold text-slate-700">{{ Auth::user()->first_name ?? 'Karyawan' }}</p>
|
||||
<p class="text-xs text-slate-500 capitalize">{{ Auth::user()->role ?? 'User' }}</p>
|
||||
</div>
|
||||
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-700 font-bold border border-indigo-200">
|
||||
{{ substr(Auth::user()->first_name ?? 'U', 0, 1) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-l border-slate-200 pl-4">
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="text-sm font-medium text-red-500 hover:text-red-700 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
|
||||
Keluar
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
@@ -0,0 +1,70 @@
|
||||
<aside class="bg-slate-900 text-white w-64 min-h-screen flex-shrink-0 hidden md:flex flex-col relative z-20 transition-all duration-300">
|
||||
|
||||
<div class="h-16 flex items-center px-6 border-b border-slate-800 bg-slate-950">
|
||||
<div class="w-8 h-8 bg-indigo-500 rounded flex items-center justify-center font-bold text-white shadow-lg mr-3">
|
||||
T
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-sm font-bold tracking-wider uppercase text-slate-100">Tunggal Pharma</h2>
|
||||
<p class="text-[10px] text-slate-400">LMS Admin Panel</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto py-4 px-3 space-y-1">
|
||||
|
||||
@if(auth()->check() && auth()->user()->hasAnyRole(['admin', 'trainer']))
|
||||
<p class="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 mt-2">Menu Utama</p>
|
||||
|
||||
<a href="{{ route('admin.dashboard') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.dashboard') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
|
||||
<span class="text-sm font-medium">Dashboard</span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if(auth()->check() && auth()->user()->hasRole('admin'))
|
||||
<p class="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 mt-6">Master Data</p>
|
||||
|
||||
<a href="{{ route('admin.departments.index') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.departments.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
|
||||
<span class="text-sm font-medium">Departemen</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.positions.index') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.positions.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
|
||||
<span class="text-sm font-medium">Posisi (Jabatan)</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.employees.index') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.employees.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
|
||||
<span class="text-sm font-medium">Data Karyawan</span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if(auth()->check() && auth()->user()->hasAnyRole(['admin', 'trainer']))
|
||||
<p class="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 mt-6">Modul Pembelajaran</p>
|
||||
|
||||
<a href="{{ route('admin.sops.index') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.sops.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
|
||||
<span class="text-sm font-medium">Dokumen SOP</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.exams.questions') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.exams.questions') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
|
||||
<span class="text-sm font-medium">Bank Soal</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.exams.index') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.exams.index') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"></path></svg>
|
||||
<span class="text-sm font-medium">Sesi Ujian (CBT)</span>
|
||||
</a>
|
||||
|
||||
<p class="px-3 text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 mt-6">Analitik & Laporan</p>
|
||||
|
||||
<a href="{{ route('reports.training') }}" class="flex items-center px-3 py-2.5 rounded-lg transition-colors {{ request()->routeIs('admin.reports.*') ? 'bg-indigo-600 text-white' : 'text-slate-300 hover:bg-slate-800 hover:text-white' }}">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
|
||||
<span class="text-sm font-medium">Laporan Training</span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</aside>
|
||||
@@ -0,0 +1,29 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Profile') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
<livewire:profile.update-profile-information-form />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
<livewire:profile.update-password-form />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
|
||||
<div class="max-w-xl">
|
||||
<livewire:profile.delete-user-form />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>Laravel</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Styles -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="antialiased font-sans">
|
||||
<div class="bg-gray-50 text-black/50 dark:bg-black dark:text-white/50">
|
||||
<img id="background" class="absolute -left-20 top-0 max-w-[877px]" src="https://laravel.com/assets/img/welcome/background.svg" />
|
||||
<div class="relative min-h-screen flex flex-col items-center justify-center selection:bg-[#FF2D20] selection:text-white">
|
||||
<div class="relative w-full max-w-2xl px-6 lg:max-w-7xl">
|
||||
<header class="grid grid-cols-2 items-center gap-2 py-10 lg:grid-cols-3">
|
||||
<div class="flex lg:justify-center lg:col-start-2">
|
||||
<svg class="h-12 w-auto text-white lg:h-16 lg:text-[#FF2D20]" viewBox="0 0 62 65" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M61.8548 14.6253C61.8778 14.7102 61.8895 14.7978 61.8897 14.8858V28.5615C61.8898 28.737 61.8434 28.9095 61.7554 29.0614C61.6675 29.2132 61.5409 29.3392 61.3887 29.4265L49.9104 36.0351V49.1337C49.9104 49.4902 49.7209 49.8192 49.4118 49.9987L25.4519 63.7916C25.3971 63.8227 25.3372 63.8427 25.2774 63.8639C25.255 63.8714 25.2338 63.8851 25.2101 63.8913C25.0426 63.9354 24.8666 63.9354 24.6991 63.8913C24.6716 63.8838 24.6467 63.8689 24.6205 63.8589C24.5657 63.8389 24.5084 63.8215 24.456 63.7916L0.501061 49.9987C0.348882 49.9113 0.222437 49.7853 0.134469 49.6334C0.0465019 49.4816 0.000120578 49.3092 0 49.1337L0 8.10652C0 8.01678 0.0124642 7.92953 0.0348998 7.84477C0.0423783 7.8161 0.0598282 7.78993 0.0697995 7.76126C0.0884958 7.70891 0.105946 7.65531 0.133367 7.6067C0.152063 7.5743 0.179485 7.54812 0.20192 7.51821C0.230588 7.47832 0.256763 7.43719 0.290416 7.40229C0.319084 7.37362 0.356476 7.35243 0.388883 7.32751C0.425029 7.29759 0.457436 7.26518 0.498568 7.2415L12.4779 0.345059C12.6296 0.257786 12.8015 0.211853 12.9765 0.211853C13.1515 0.211853 13.3234 0.257786 13.475 0.345059L25.4531 7.2415H25.4556C25.4955 7.26643 25.5292 7.29759 25.5653 7.32626C25.5977 7.35119 25.6339 7.37362 25.6625 7.40104C25.6974 7.43719 25.7224 7.47832 25.7523 7.51821C25.7735 7.54812 25.8021 7.5743 25.8196 7.6067C25.8483 7.65656 25.8645 7.70891 25.8844 7.76126C25.8944 7.78993 25.9118 7.8161 25.9193 7.84602C25.9423 7.93096 25.954 8.01853 25.9542 8.10652V33.7317L35.9355 27.9844V14.8846C35.9355 14.7973 35.948 14.7088 35.9704 14.6253C35.9792 14.5954 35.9954 14.5692 36.0053 14.5405C36.0253 14.4882 36.0427 14.4346 36.0702 14.386C36.0888 14.3536 36.1163 14.3274 36.1375 14.2975C36.1674 14.2576 36.1923 14.2165 36.2272 14.1816C36.2559 14.1529 36.292 14.1317 36.3244 14.1068C36.3618 14.0769 36.3942 14.0445 36.4341 14.0208L48.4147 7.12434C48.5663 7.03694 48.7383 6.99094 48.9133 6.99094C49.0883 6.99094 49.2602 7.03694 49.4118 7.12434L61.3899 14.0208C61.4323 14.0457 61.4647 14.0769 61.5021 14.1055C61.5333 14.1305 61.5694 14.1529 61.5981 14.1803C61.633 14.2165 61.6579 14.2576 61.6878 14.2975C61.7103 14.3274 61.7377 14.3536 61.7551 14.386C61.7838 14.4346 61.8 14.4882 61.8199 14.5405C61.8312 14.5692 61.8474 14.5954 61.8548 14.6253ZM59.893 27.9844V16.6121L55.7013 19.0252L49.9104 22.3593V33.7317L59.8942 27.9844H59.893ZM47.9149 48.5566V37.1768L42.2187 40.4299L25.953 49.7133V61.2003L47.9149 48.5566ZM1.99677 9.83281V48.5566L23.9562 61.199V49.7145L12.4841 43.2219L12.4804 43.2194L12.4754 43.2169C12.4368 43.1945 12.4044 43.1621 12.3682 43.1347C12.3371 43.1097 12.3009 43.0898 12.2735 43.0624L12.271 43.0586C12.2386 43.0275 12.2162 42.9888 12.1887 42.9539C12.1638 42.9203 12.1339 42.8916 12.114 42.8567L12.1127 42.853C12.0903 42.8156 12.0766 42.7707 12.0604 42.7283C12.0442 42.6909 12.023 42.656 12.013 42.6161C12.0005 42.5688 11.998 42.5177 11.9931 42.4691C11.9881 42.4317 11.9781 42.3943 11.9781 42.3569V15.5801L6.18848 12.2446L1.99677 9.83281ZM12.9777 2.36177L2.99764 8.10652L12.9752 13.8513L22.9541 8.10527L12.9752 2.36177H12.9777ZM18.1678 38.2138L23.9574 34.8809V9.83281L19.7657 12.2459L13.9749 15.5801V40.6281L18.1678 38.2138ZM48.9133 9.14105L38.9344 14.8858L48.9133 20.6305L58.8909 14.8846L48.9133 9.14105ZM47.9149 22.3593L42.124 19.0252L37.9323 16.6121V27.9844L43.7219 31.3174L47.9149 33.7317V22.3593ZM24.9533 47.987L39.59 39.631L46.9065 35.4555L36.9352 29.7145L25.4544 36.3242L14.9907 42.3482L24.9533 47.987Z" fill="currentColor"/></svg>
|
||||
</div>
|
||||
@if (Route::has('login'))
|
||||
<livewire:welcome.navigation />
|
||||
@endif
|
||||
</header>
|
||||
|
||||
<main class="mt-6">
|
||||
<div class="grid gap-6 lg:grid-cols-2 lg:gap-8">
|
||||
<a
|
||||
href="https://laravel.com/docs"
|
||||
id="docs-card"
|
||||
class="flex flex-col items-start gap-6 overflow-hidden rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] transition duration-300 hover:text-black/70 hover:ring-black/20 focus:outline-none focus-visible:ring-[#FF2D20] md:row-span-3 lg:p-10 lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 dark:hover:text-white/70 dark:hover:ring-zinc-700 dark:focus-visible:ring-[#FF2D20]"
|
||||
>
|
||||
<div id="screenshot-container" class="relative flex w-full flex-1 items-stretch">
|
||||
<img
|
||||
src="https://laravel.com/assets/img/welcome/docs-light.svg"
|
||||
alt="Laravel documentation screenshot"
|
||||
class="aspect-video h-full w-full flex-1 rounded-[10px] object-top object-cover drop-shadow-[0px_4px_34px_rgba(0,0,0,0.06)] dark:hidden"
|
||||
onerror="
|
||||
document.getElementById('screenshot-container').classList.add('!hidden');
|
||||
document.getElementById('docs-card').classList.add('!row-span-1');
|
||||
document.getElementById('docs-card-content').classList.add('!flex-row');
|
||||
document.getElementById('background').classList.add('!hidden');
|
||||
"
|
||||
/>
|
||||
<img
|
||||
src="https://laravel.com/assets/img/welcome/docs-dark.svg"
|
||||
alt="Laravel documentation screenshot"
|
||||
class="hidden aspect-video h-full w-full flex-1 rounded-[10px] object-top object-cover drop-shadow-[0px_4px_34px_rgba(0,0,0,0.25)] dark:block"
|
||||
/>
|
||||
<div
|
||||
class="absolute -bottom-16 -left-16 h-40 w-[calc(100%+8rem)] bg-gradient-to-b from-transparent via-white to-white dark:via-zinc-900 dark:to-zinc-900"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex items-center gap-6 lg:items-end">
|
||||
<div id="docs-card-content" class="flex items-start gap-6 lg:flex-col">
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#FF2D20" d="M23 4a1 1 0 0 0-1.447-.894L12.224 7.77a.5.5 0 0 1-.448 0L2.447 3.106A1 1 0 0 0 1 4v13.382a1.99 1.99 0 0 0 1.105 1.79l9.448 4.728c.14.065.293.1.447.1.154-.005.306-.04.447-.105l9.453-4.724a1.99 1.99 0 0 0 1.1-1.789V4ZM3 6.023a.25.25 0 0 1 .362-.223l7.5 3.75a.251.251 0 0 1 .138.223v11.2a.25.25 0 0 1-.362.224l-7.5-3.75a.25.25 0 0 1-.138-.22V6.023Zm18 11.2a.25.25 0 0 1-.138.224l-7.5 3.75a.249.249 0 0 1-.329-.099.249.249 0 0 1-.033-.12V9.772a.251.251 0 0 1 .138-.224l7.5-3.75a.25.25 0 0 1 .362.224v11.2Z"/><path fill="#FF2D20" d="m3.55 1.893 8 4.048a1.008 1.008 0 0 0 .9 0l8-4.048a1 1 0 0 0-.9-1.785l-7.322 3.706a.506.506 0 0 1-.452 0L4.454.108a1 1 0 0 0-.9 1.785H3.55Z"/></svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5 lg:pt-0">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Documentation</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laravel has wonderful documentation covering every aspect of the framework. Whether you are a newcomer or have prior experience with Laravel, we recommend reading our documentation from beginning to end.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg class="size-6 shrink-0 stroke-[#FF2D20]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"/></svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://laracasts.com"
|
||||
class="flex items-start gap-4 rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] transition duration-300 hover:text-black/70 hover:ring-black/20 focus:outline-none focus-visible:ring-[#FF2D20] lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 dark:hover:text-white/70 dark:hover:ring-zinc-700 dark:focus-visible:ring-[#FF2D20]"
|
||||
>
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><g fill="#FF2D20"><path d="M24 8.25a.5.5 0 0 0-.5-.5H.5a.5.5 0 0 0-.5.5v12a2.5 2.5 0 0 0 2.5 2.5h19a2.5 2.5 0 0 0 2.5-2.5v-12Zm-7.765 5.868a1.221 1.221 0 0 1 0 2.264l-6.626 2.776A1.153 1.153 0 0 1 8 18.123v-5.746a1.151 1.151 0 0 1 1.609-1.035l6.626 2.776ZM19.564 1.677a.25.25 0 0 0-.177-.427H15.6a.106.106 0 0 0-.072.03l-4.54 4.543a.25.25 0 0 0 .177.427h3.783c.027 0 .054-.01.073-.03l4.543-4.543ZM22.071 1.318a.047.047 0 0 0-.045.013l-4.492 4.492a.249.249 0 0 0 .038.385.25.25 0 0 0 .14.042h5.784a.5.5 0 0 0 .5-.5v-2a2.5 2.5 0 0 0-1.925-2.432ZM13.014 1.677a.25.25 0 0 0-.178-.427H9.101a.106.106 0 0 0-.073.03l-4.54 4.543a.25.25 0 0 0 .177.427H8.4a.106.106 0 0 0 .073-.03l4.54-4.543ZM6.513 1.677a.25.25 0 0 0-.177-.427H2.5A2.5 2.5 0 0 0 0 3.75v2a.5.5 0 0 0 .5.5h1.4a.106.106 0 0 0 .073-.03l4.54-4.543Z"/></g></svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Laracasts</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<svg class="size-6 shrink-0 self-center stroke-[#FF2D20]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"/></svg>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://laravel-news.com"
|
||||
class="flex items-start gap-4 rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] transition duration-300 hover:text-black/70 hover:ring-black/20 focus:outline-none focus-visible:ring-[#FF2D20] lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800 dark:hover:text-white/70 dark:hover:ring-zinc-700 dark:focus-visible:ring-[#FF2D20]"
|
||||
>
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><g fill="#FF2D20"><path d="M8.75 4.5H5.5c-.69 0-1.25.56-1.25 1.25v4.75c0 .69.56 1.25 1.25 1.25h3.25c.69 0 1.25-.56 1.25-1.25V5.75c0-.69-.56-1.25-1.25-1.25Z"/><path d="M24 10a3 3 0 0 0-3-3h-2V2.5a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2V20a3.5 3.5 0 0 0 3.5 3.5h17A3.5 3.5 0 0 0 24 20V10ZM3.5 21.5A1.5 1.5 0 0 1 2 20V3a.5.5 0 0 1 .5-.5h14a.5.5 0 0 1 .5.5v17c0 .295.037.588.11.874a.5.5 0 0 1-.484.625L3.5 21.5ZM22 20a1.5 1.5 0 1 1-3 0V9.5a.5.5 0 0 1 .5-.5H21a1 1 0 0 1 1 1v10Z"/><path d="M12.751 6.047h2a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-2A.75.75 0 0 1 12 7.3v-.5a.75.75 0 0 1 .751-.753ZM12.751 10.047h2a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-2A.75.75 0 0 1 12 11.3v-.5a.75.75 0 0 1 .751-.753ZM4.751 14.047h10a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-10A.75.75 0 0 1 4 15.3v-.5a.75.75 0 0 1 .751-.753ZM4.75 18.047h7.5a.75.75 0 0 1 .75.75v.5a.75.75 0 0 1-.75.75h-7.5A.75.75 0 0 1 4 19.3v-.5a.75.75 0 0 1 .75-.753Z"/></g></svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Laravel News</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<svg class="size-6 shrink-0 self-center stroke-[#FF2D20]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75"/></svg>
|
||||
</a>
|
||||
|
||||
<div class="flex items-start gap-4 rounded-lg bg-white p-6 shadow-[0px_14px_34px_0px_rgba(0,0,0,0.08)] ring-1 ring-white/[0.05] lg:pb-10 dark:bg-zinc-900 dark:ring-zinc-800">
|
||||
<div class="flex size-12 shrink-0 items-center justify-center rounded-full bg-[#FF2D20]/10 sm:size-16">
|
||||
<svg class="size-5 sm:size-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<g fill="#FF2D20">
|
||||
<path
|
||||
d="M16.597 12.635a.247.247 0 0 0-.08-.237 2.234 2.234 0 0 1-.769-1.68c.001-.195.03-.39.084-.578a.25.25 0 0 0-.09-.267 8.8 8.8 0 0 0-4.826-1.66.25.25 0 0 0-.268.181 2.5 2.5 0 0 1-2.4 1.824.045.045 0 0 0-.045.037 12.255 12.255 0 0 0-.093 3.86.251.251 0 0 0 .208.214c2.22.366 4.367 1.08 6.362 2.118a.252.252 0 0 0 .32-.079 10.09 10.09 0 0 0 1.597-3.733ZM13.616 17.968a.25.25 0 0 0-.063-.407A19.697 19.697 0 0 0 8.91 15.98a.25.25 0 0 0-.287.325c.151.455.334.898.548 1.328.437.827.981 1.594 1.619 2.28a.249.249 0 0 0 .32.044 29.13 29.13 0 0 0 2.506-1.99ZM6.303 14.105a.25.25 0 0 0 .265-.274 13.048 13.048 0 0 1 .205-4.045.062.062 0 0 0-.022-.07 2.5 2.5 0 0 1-.777-.982.25.25 0 0 0-.271-.149 11 11 0 0 0-5.6 2.815.255.255 0 0 0-.075.163c-.008.135-.02.27-.02.406.002.8.084 1.598.246 2.381a.25.25 0 0 0 .303.193 19.924 19.924 0 0 1 5.746-.438ZM9.228 20.914a.25.25 0 0 0 .1-.393 11.53 11.53 0 0 1-1.5-2.22 12.238 12.238 0 0 1-.91-2.465.248.248 0 0 0-.22-.187 18.876 18.876 0 0 0-5.69.33.249.249 0 0 0-.179.336c.838 2.142 2.272 4 4.132 5.353a.254.254 0 0 0 .15.048c1.41-.01 2.807-.282 4.117-.802ZM18.93 12.957l-.005-.008a.25.25 0 0 0-.268-.082 2.21 2.21 0 0 1-.41.081.25.25 0 0 0-.217.2c-.582 2.66-2.127 5.35-5.75 7.843a.248.248 0 0 0-.09.299.25.25 0 0 0 .065.091 28.703 28.703 0 0 0 2.662 2.12.246.246 0 0 0 .209.037c2.579-.701 4.85-2.242 6.456-4.378a.25.25 0 0 0 .048-.189 13.51 13.51 0 0 0-2.7-6.014ZM5.702 7.058a.254.254 0 0 0 .2-.165A2.488 2.488 0 0 1 7.98 5.245a.093.093 0 0 0 .078-.062 19.734 19.734 0 0 1 3.055-4.74.25.25 0 0 0-.21-.41 12.009 12.009 0 0 0-10.4 8.558.25.25 0 0 0 .373.281 12.912 12.912 0 0 1 4.826-1.814ZM10.773 22.052a.25.25 0 0 0-.28-.046c-.758.356-1.55.635-2.365.833a.25.25 0 0 0-.022.48c1.252.43 2.568.65 3.893.65.1 0 .2 0 .3-.008a.25.25 0 0 0 .147-.444c-.526-.424-1.1-.917-1.673-1.465ZM18.744 8.436a.249.249 0 0 0 .15.228 2.246 2.246 0 0 1 1.352 2.054c0 .337-.08.67-.23.972a.25.25 0 0 0 .042.28l.007.009a15.016 15.016 0 0 1 2.52 4.6.25.25 0 0 0 .37.132.25.25 0 0 0 .096-.114c.623-1.464.944-3.039.945-4.63a12.005 12.005 0 0 0-5.78-10.258.25.25 0 0 0-.373.274c.547 2.109.85 4.274.901 6.453ZM9.61 5.38a.25.25 0 0 0 .08.31c.34.24.616.561.8.935a.25.25 0 0 0 .3.127.631.631 0 0 1 .206-.034c2.054.078 4.036.772 5.69 1.991a.251.251 0 0 0 .267.024c.046-.024.093-.047.141-.067a.25.25 0 0 0 .151-.23A29.98 29.98 0 0 0 15.957.764a.25.25 0 0 0-.16-.164 11.924 11.924 0 0 0-2.21-.518.252.252 0 0 0-.215.076A22.456 22.456 0 0 0 9.61 5.38Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 sm:pt-5">
|
||||
<h2 class="text-xl font-semibold text-black dark:text-white">Vibrant Ecosystem</h2>
|
||||
|
||||
<p class="mt-4 text-sm/relaxed">
|
||||
Laravel's robust library of first-party tools and libraries, such as <a href="https://forge.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white dark:focus-visible:ring-[#FF2D20]">Forge</a>, <a href="https://vapor.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Vapor</a>, <a href="https://nova.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Nova</a>, <a href="https://envoyer.io" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Envoyer</a>, and <a href="https://herd.laravel.com" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Herd</a> help you take your projects to the next level. Pair them with powerful open source libraries like <a href="https://laravel.com/docs/billing" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Cashier</a>, <a href="https://laravel.com/docs/dusk" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Dusk</a>, <a href="https://laravel.com/docs/broadcasting" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Echo</a>, <a href="https://laravel.com/docs/horizon" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Horizon</a>, <a href="https://laravel.com/docs/sanctum" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Sanctum</a>, <a href="https://laravel.com/docs/telescope" class="rounded-sm underline hover:text-black focus:outline-none focus-visible:ring-1 focus-visible:ring-[#FF2D20] dark:hover:text-white">Telescope</a>, and more.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="py-16 text-center text-sm text-black dark:text-white/70">
|
||||
Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user