database

B.M. Restu Pandhu P.

/ 11

database

UserFactory

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * @extends Factory<User>
 */
class UserFactory extends Factory
{
    /**
     * The current password being used by the factory.
     */
    protected static ?string $password;

    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'username' => fake()->unique()->userName(),
            'email' => fake()->unique()->safeEmail(),
            'role' => 'technician',
            'is_active' => true,
            'password_must_change' => false,
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}

DatabaseSeeder

<?php

namespace Database\Seeders;

use App\Models\Config;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        Config::create([
            'company_name' => 'Bhinneka Tech',
            'company_email' => 'info@bhinneka.tech',
        ]);

        User::factory()->create([
            'name' => 'Admin',
            'username' => 'admin',
            'email' => 'admin@helpdesk.test',
            'role' => 'admin',
            'password' => 'admin123',
            'password_must_change' => false,
        ]);

        User::factory()->create([
            'name' => 'Restu',
            'username' => 'restu',
            'email' => 'restu@helpdesk.test',
            'role' => 'technician',
            'password' => 'teknisi123',
            'password_must_change' => false,
        ]);
    }
}

route

web.php

<?php

use App\Http\Controllers\Admin\AdminUserController;
use App\Http\Controllers\Admin\ApplicationController;
use App\Http\Controllers\Admin\ClientController;
use App\Http\Controllers\Admin\ComplaintController as AdminComplaintController;
use App\Http\Controllers\Admin\DashboardController;
use App\Http\Controllers\Admin\RecapController;
use App\Http\Controllers\Admin\SettingController;
use App\Http\Controllers\Admin\TechnicianController;
use App\Http\Controllers\Auth\ForceChangePasswordController;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\Auth\LoginController;
use App\Http\Controllers\ComplaintController;
use App\Http\Controllers\LegalController;
use App\Http\Controllers\ReportVerificationController;
use App\Http\Controllers\Technician\TicketController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::redirect('/', '/complaint');

Route::get('/complaint', [ComplaintController::class, 'create'])->name('complaint.create');
Route::get('/complaint/track', [ComplaintController::class, 'trackForm'])->name('complaint.track.form');

Route::middleware('throttle:10,60')->group(function () {
    Route::post('/complaint', [ComplaintController::class, 'store'])->name('complaint.store');
    Route::post('/complaint/track', [ComplaintController::class, 'track'])->name('complaint.track');
});

Route::get('/kebijakan-privasi', [LegalController::class, 'privacy'])->name('legal.privacy');
Route::get('/syarat-ketentuan', [LegalController::class, 'terms'])->name('legal.terms');

Route::get('/v/{token}', [ReportVerificationController::class, 'show'])->name('recap.verify');

Route::middleware('guest')->group(function () {
    Route::get('/login', [LoginController::class, 'create'])->name('login');
    Route::post('/login', [LoginController::class, 'store'])->name('login.store');

    Route::get('/forgot-password', [ForgotPasswordController::class, 'showEmailForm'])->name('password.forgot');
    Route::get('/forgot-password/verify', [ForgotPasswordController::class, 'showVerifyForm'])->name('password.forgot.verify');
    Route::get('/forgot-password/reset', [ForgotPasswordController::class, 'showResetForm'])->name('password.forgot.reset');

    Route::middleware('throttle:5,60')->group(function () {
        Route::post('/forgot-password', [ForgotPasswordController::class, 'sendCode'])->name('password.forgot.send');
        Route::post('/forgot-password/resend', [ForgotPasswordController::class, 'resend'])->name('password.forgot.resend');
    });
    Route::post('/forgot-password/verify', [ForgotPasswordController::class, 'verifyCode'])->name('password.forgot.verify.submit');
    Route::post('/forgot-password/reset', [ForgotPasswordController::class, 'resetPassword'])->name('password.forgot.reset.submit');
});

Route::middleware('auth')->group(function () {
    Route::post('/logout', [LoginController::class, 'destroy'])->name('logout');

    Route::get('/password/force-change', [ForceChangePasswordController::class, 'create'])->name('password.force-change');
    Route::post('/password/force-change', [ForceChangePasswordController::class, 'store'])->name('password.force-change.store');

    Route::get('/login/redirect', function () {
        $user = Auth::user();

        return redirect()->to($user->isAdmin() ? route('admin.dashboard') : route('technician.tickets.index'));
    })->name('login.redirect');
});

Route::middleware(['auth', 'role:admin'])->prefix('admin')->name('admin.')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');

    Route::get('/technicians', [TechnicianController::class, 'index'])->name('technicians.index');
    Route::post('/technicians', [TechnicianController::class, 'store'])->name('technicians.store');
    Route::put('/technicians/{technician}', [TechnicianController::class, 'update'])->name('technicians.update');
    Route::patch('/technicians/{technician}/toggle-active', [TechnicianController::class, 'toggleActive'])->name('technicians.toggle-active');

    Route::get('/admins', [AdminUserController::class, 'index'])->name('admins.index');
    Route::post('/admins', [AdminUserController::class, 'store'])->name('admins.store');
    Route::put('/admins/{admin}', [AdminUserController::class, 'update'])->name('admins.update');
    Route::patch('/admins/{admin}/toggle-active', [AdminUserController::class, 'toggleActive'])->name('admins.toggle-active');

    Route::get('/clients', [ClientController::class, 'index'])->name('clients.index');
    Route::post('/clients', [ClientController::class, 'store'])->name('clients.store');
    Route::put('/clients/{client}', [ClientController::class, 'update'])->name('clients.update');
    Route::patch('/clients/{client}/toggle-active', [ClientController::class, 'toggleActive'])->name('clients.toggle-active');

    Route::get('/applications', [ApplicationController::class, 'index'])->name('applications.index');
    Route::post('/applications', [ApplicationController::class, 'store'])->name('applications.store');
    Route::put('/applications/{application}', [ApplicationController::class, 'update'])->name('applications.update');
    Route::patch('/applications/{application}/toggle-active', [ApplicationController::class, 'toggleActive'])->name('applications.toggle-active');

    Route::get('/complaints', [AdminComplaintController::class, 'index'])->name('complaints.index');
    Route::get('/complaints/{complaint}', [AdminComplaintController::class, 'show'])->name('complaints.show');
    Route::post('/complaints/{complaint}/close', [AdminComplaintController::class, 'close'])->name('complaints.close');

    Route::get('/recap', [RecapController::class, 'index'])->name('recap.index');
    Route::get('/recap/show', [RecapController::class, 'show'])->name('recap.show');

    Route::get('/settings', [SettingController::class, 'edit'])->name('settings.edit');
    Route::post('/settings', [SettingController::class, 'update'])->name('settings.update');
});

Route::middleware(['auth', 'role:technician'])->prefix('technician')->name('technician.')->group(function () {
    Route::get('/tickets', [TicketController::class, 'index'])->name('tickets.index');
    Route::get('/tickets/{complaint}', [TicketController::class, 'show'])->name('tickets.show');
    Route::post('/tickets/{complaint}/claim', [TicketController::class, 'claim'])->name('tickets.claim');
    Route::post('/tickets/{complaint}/release', [TicketController::class, 'release'])->name('tickets.release');
    Route::post('/tickets/{complaint}/progress', [TicketController::class, 'progress'])->name('tickets.progress');
    Route::post('/tickets/{complaint}/resolve', [TicketController::class, 'resolve'])->name('tickets.resolve');
});

view

view2

admin

admin

@php($modalId = 'modal-admin')

<x-layout title="Kelola Admin">
    <div class="mb-4 flex items-center justify-between">
        <h1 class="text-lg font-semibold text-brand-navy">Kelola Admin</h1>
        <button type="button" data-modal-open="{{ $modalId }}" data-modal-title="Tambah Admin"
            data-form-action="{{ route('admin.admins.store') }}" data-form-method="POST"
            class="rounded-lg bg-brand-navy px-4 py-2 text-sm font-semibold text-white hover:bg-brand-navy-dark">
            + Tambah Admin
        </button>
    </div>

    <div class="overflow-hidden rounded-xl border border-slate-200 bg-white">
        <table class="w-full text-sm">
            <thead class="text-left text-xs text-slate-500">
                <tr>
                    <th class="px-4 py-2">Nama</th>
                    <th class="px-4 py-2">Username</th>
                    <th class="px-4 py-2">Email</th>
                    <th class="px-4 py-2">Status</th>
                    <th class="px-4 py-2"></th>
                </tr>
            </thead>
            <tbody class="divide-y divide-slate-100">
                @forelse ($admins as $admin)
                    <tr>
                        <td class="px-4 py-2">{{ $admin->name }}</td>
                        <td class="px-4 py-2">{{ $admin->username }}</td>
                        <td class="px-4 py-2">{{ $admin->email }}</td>
                        <td class="px-4 py-2">
                            <span class="rounded-full px-2 py-0.5 text-xs {{ $admin->is_active ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-200 text-slate-600' }}">
                                {{ $admin->is_active ? 'Aktif' : 'Nonaktif' }}
                            </span>
                        </td>
                        <td class="space-x-3 px-4 py-2 text-right">
                            <button type="button" data-modal-open="{{ $modalId }}" data-modal-title="Ubah Admin"
                                data-form-action="{{ route('admin.admins.update', $admin) }}" data-form-method="PUT"
                                data-field-name="{{ $admin->name }}" data-field-username="{{ $admin->username }}"
                                data-field-email="{{ $admin->email }}"
                                class="text-slate-500 hover:text-slate-800">Ubah</button>
                            <form method="POST" action="{{ route('admin.admins.toggle-active', $admin) }}" class="inline">
                                @csrf
                                @method('PATCH')
                                <button type="submit" class="text-slate-500 hover:text-red-600"
                                    onclick="return confirm('Yakin {{ $admin->is_active ? 'nonaktifkan' : 'aktifkan' }} akun ini?')">
                                    {{ $admin->is_active ? 'Nonaktifkan' : 'Aktifkan' }}
                                </button>
                            </form>
                        </td>
                    </tr>
                @empty
                    <tr><td colspan="5" class="px-4 py-6 text-center text-slate-400">Belum ada admin lain.</td></tr>
                @endforelse
            </tbody>
        </table>
    </div>

    <x-modal :id="$modalId" title="Tambah Admin">
        @if ($errors->any() && old('_modal') === $modalId)
            <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
                <ul class="list-inside list-disc">
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif

        <form method="POST" action="{{ route('admin.admins.store') }}" class="space-y-4">
            @csrf
            <input type="hidden" name="_method" value="POST">
            <input type="hidden" name="_modal" value="{{ $modalId }}">
            <div>
                <label class="mb-1 block text-sm font-medium text-slate-700">Nama</label>
                <input type="text" name="name" required class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm">
            </div>
            <div>
                <label class="mb-1 block text-sm font-medium text-slate-700">Username</label>
                <input type="text" name="username" required class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm">
            </div>
            <div>
                <label class="mb-1 block text-sm font-medium text-slate-700">Email</label>
                <input type="email" name="email" required class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm">
            </div>
            <p class="text-xs text-slate-400">Password digenerate otomatis dan dikirim ke email di atas.</p>
            <div class="flex justify-end gap-2 pt-2">
                <button type="button" data-modal-close class="rounded-lg px-4 py-2 text-sm text-slate-500 hover:bg-slate-100">Batal</button>
                <button type="submit" class="rounded-lg bg-brand-navy px-4 py-2 text-sm font-semibold text-white hover:bg-brand-navy-dark">Simpan</button>
            </div>
        </form>
    </x-modal>
</x-layout>

login

11.

<x-auth-shell title="Login" :company="$company">
    <h1 class="text-xl font-bold text-gray-900 mb-2">Selamat Datang! 👋</h1>
    <p class="text-gray-600 text-xs mb-3">
        Silakan masuk ke akun Anda untuk mulai mengelola tiket pengaduan.
    </p>
    <p class="text-gray-500 text-xs mb-4">
        Gunakan username atau email yang terdaftar di sistem untuk masuk. Belum punya akun? Hubungi admin.
    </p>

    @if ($errors->any())
        <div class="mb-3 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700">
            {{ $errors->first() }}
        </div>
    @endif

    <form method="POST" action="{{ route('login.store') }}" class="space-y-3">
        @csrf

        <div class="relative">
            <label for="login" class="absolute -top-2 left-3 px-1 bg-white text-[11px] font-medium text-gray-500">
                Username / Email
            </label>
            <input id="login" name="login" type="text" required autofocus value="{{ old('login') }}"
                class="block w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-navy focus:border-transparent placeholder-gray-400 transition-all duration-200 text-xs"
                placeholder="Masukkan username atau email">
        </div>

        <div class="relative">
            <label for="password" class="absolute -top-2 left-3 px-1 bg-white text-[11px] font-medium text-gray-500">
                Password
            </label>
            <input id="password" name="password" type="password" required
                class="block w-full px-3 py-2.5 pr-10 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-navy focus:border-transparent placeholder-gray-400 transition-all duration-200 text-xs"
                placeholder="Masukkan password">
            <button type="button" data-toggle-password="password"
                class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none">
                <svg data-eye-open xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12s3.75-7.5 9.75-7.5 9.75 7.5 9.75 7.5-3.75 7.5-9.75 7.5S2.25 12 2.25 12Z" />
                    <path stroke-linecap="round" stroke-linejoin="round" d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
                </svg>
                <svg data-eye-closed hidden xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M3 3l18 18M10.6 10.6a3 3 0 0 0 4.24 4.24M6.6 6.6C4.4 8 2.9 10 2.25 12c0 0 3.75 7.5 9.75 7.5 1.96 0 3.7-.58 5.15-1.4M9.9 4.8A10.4 10.4 0 0 1 12 4.5c6 0 9.75 7.5 9.75 7.5a15.4 15.4 0 0 1-2.42 3.31" />
                </svg>
            </button>
        </div>

        <div class="flex items-center justify-between">
            <label class="flex cursor-pointer items-center gap-2">
                <span class="relative h-4 w-4 flex-shrink-0">
                    <input id="remember" name="remember" type="checkbox" class="peer absolute inset-0 z-10 h-4 w-4 cursor-pointer opacity-0">
                    <span class="pointer-events-none absolute inset-0 rounded-md border-2 border-gray-300 bg-white transition peer-checked:border-brand-blue peer-checked:bg-brand-navy"></span>
                    <svg xmlns="http://www.w3.org/2000/svg" class="pointer-events-none absolute inset-0 m-auto h-2.5 w-2.5 text-white opacity-0 transition peer-checked:opacity-100" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4">
                        <path stroke-linecap="round" stroke-linejoin="round" d="m5 13 4 4L19 7" />
                    </svg>
                </span>
                <span class="text-xs text-gray-600">Ingat saya</span>
            </label>
            <a href="{{ route('password.forgot') }}" class="text-xs font-medium text-brand-navy hover:text-brand-navy-dark">Lupa password?</a>
        </div>

        <button type="submit"
            class="w-full flex items-center justify-center gap-2 py-2.5 px-4 rounded-lg text-xs font-semibold text-white bg-brand-navy hover:bg-brand-navy-dark focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-brand-navy transition-colors duration-200 shadow-sm">
            MASUK
        </button>
    </form>
</x-auth-shell>

Komentar

Belum ada komentar.