chore: apply code review checklist and web design guidelines fixes

This commit is contained in:
ramram1515
2026-06-29 13:53:07 +08:00
parent 2c7c3528e4
commit 9ac2ea7bda
6 changed files with 51 additions and 11 deletions

View File

@@ -9,14 +9,32 @@ use App\Http\Resources\UserDetailResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class AdminUserController extends ApiController
{
public function index(Request $request): JsonResponse
{
$request->validate([
'role' => ['nullable', 'in:admin,resident,driver,helper,scanner,store_partner'],
'status' => ['nullable', 'in:active,suspended,pending'],
'role' => [
'nullable',
Rule::in([
User::ROLE_ADMIN,
User::ROLE_RESIDENT,
User::ROLE_DRIVER,
User::ROLE_HELPER,
User::ROLE_SCANNER,
User::ROLE_STORE_PARTNER,
])
],
'status' => [
'nullable',
Rule::in([
User::STATUS_ACTIVE,
User::STATUS_SUSPENDED,
User::STATUS_PENDING,
])
],
'verification_status' => ['nullable', 'in:pending,approved,rejected'],
'q' => ['nullable', 'string', 'max:100'],
'per_page' => ['nullable', 'integer', 'min:1', 'max:500'],

View File

@@ -22,6 +22,7 @@ class DumpsiteResource extends JsonResource
return [
'id' => $this->uuid,
'db_id' => $this->id,
'name' => $this->name,
'code' => $this->code,
'address_line' => $this->address_line,

View File

@@ -15,7 +15,7 @@ class SuperAdminSeeder extends Seeder
['email' => 'super@verde.local'],
[
'phone' => '+639000000099',
'password' => Hash::make('password'),
'password' => Hash::make(env('SUPER_ADMIN_PASSWORD', 'password')),
'first_name' => 'Verde',
'last_name' => 'Super Admin',
'role' => User::ROLE_SUPER_ADMIN,
@@ -30,6 +30,6 @@ class SuperAdminSeeder extends Seeder
$user->syncRoles([User::ROLE_SUPER_ADMIN]);
NotificationPreference::firstOrCreate(['user_id' => $user->id]);
$this->command->info("Super admin: {$user->email} (password: password)");
$this->command->info("Super admin: {$user->email} (password: ".env('SUPER_ADMIN_PASSWORD', 'password').")");
}
}

View File

@@ -37,11 +37,11 @@
<div class="slide-over-panel translate-x-0 p-6 overflow-y-auto">
<div class="mb-4 flex items-center justify-between">
<h3 class="text-base font-semibold text-neutral-900">New dumpsite</h3>
<button data-close class="text-neutral-400 hover:text-neutral-600">
<button data-close aria-label="Close" class="text-neutral-400 hover:text-neutral-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
</button>
</div>
<form id="create-form" class="space-y-4">
<form id="create-form" class="space-y-4" autocomplete="off">
<div><label class="form-label">Name</label><input name="name" required class="form-input"></div>
<div><label class="form-label">Code</label><input name="code" required class="form-input" placeholder="DS-..."></div>
<div><label class="form-label">Address</label><input name="address_line" required class="form-input"></div>
@@ -83,7 +83,13 @@
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" data-close class="btn-ghost">Cancel</button>
<button type="submit" class="btn-primary">Create</button>
<button type="submit" id="submit-btn" class="btn-primary">
<span class="btn-label">Create</span>
<svg class="btn-spinner ml-2 hidden h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z"/>
</svg>
</button>
</div>
</form>
</div>
@@ -125,7 +131,7 @@
div.innerHTML = `
<input type="number" step="0.000001" placeholder="lat" value="${lat}" class="form-input flex-1 boundary-lat" required>
<input type="number" step="0.000001" placeholder="lng" value="${lng}" class="form-input flex-1 boundary-lng" required>
<button type="button" class="btn-ghost px-2 remove-point">×</button>
<button type="button" class="btn-ghost px-2 remove-point" aria-label="Remove point">×</button>
`;
div.querySelector('.remove-point').addEventListener('click', () => div.remove());
pointsEl.appendChild(div);
@@ -285,6 +291,15 @@
document.getElementById('create-form').addEventListener('submit', async (e) => {
e.preventDefault();
const submitBtn = document.getElementById('submit-btn');
const btnLabel = submitBtn.querySelector('.btn-label');
const btnSpinner = submitBtn.querySelector('.btn-spinner');
submitBtn.disabled = true;
btnLabel.textContent = 'Creating…';
btnSpinner.classList.remove('hidden');
const fd = new FormData(e.target);
const payload = Object.fromEntries(fd.entries());
payload.lat = parseFloat(payload.lat);
@@ -299,6 +314,11 @@
method: 'POST',
body: JSON.stringify(payload),
});
submitBtn.disabled = false;
btnLabel.textContent = 'Create';
btnSpinner.classList.add('hidden');
if (res.ok) { window.Verde.toast('Dumpsite created', 'success'); modal.classList.add('hidden'); e.target.reset(); load(); }
else window.Verde.toast(res.body?.message ?? 'Create failed', 'error');
});

View File

@@ -99,7 +99,7 @@
if (!res.ok) return;
(res.body.data ?? []).forEach(d => {
const opt = document.createElement('option');
opt.value = d.id;
opt.value = d.db_id;
opt.textContent = d.name;
dumpsiteSelect.appendChild(opt);
});
@@ -115,7 +115,7 @@
function renderPicker() {
if (allDops.length === 0) { stopsPicker.innerHTML = '<div class="text-neutral-400">No active DOPs found.</div>'; return; }
stopsPicker.innerHTML = allDops.map(d => `
<button type="button" data-id="${d.id}" data-name="${window.Verde.escapeHtml(d.name)}"
<button type="button" data-id="${d.db_id}" data-name="${window.Verde.escapeHtml(d.name)}"
class="dop-pick block w-full rounded px-2 py-1 text-left transition hover:bg-white">
<span class="font-mono text-[10px] text-neutral-500">${window.Verde.escapeHtml(d.code)}</span>
<span class="ml-2">${window.Verde.escapeHtml(d.name)}</span>

View File

@@ -58,6 +58,7 @@
type="email"
required
autocomplete="email"
spellcheck="false"
autofocus
class="form-input"
placeholder="you@verde.local">
@@ -78,7 +79,7 @@
placeholder="••••••••">
</div>
<div id="error-banner" class="hidden rounded-md border border-red-200 bg-red-50 px-3 py-2.5 text-sm text-red-700"></div>
<div id="error-banner" aria-live="polite" class="hidden rounded-md border border-red-200 bg-red-50 px-3 py-2.5 text-sm text-red-700"></div>
<button type="submit" id="submit-btn" class="btn-primary w-full">
<span class="btn-label">Sign in</span>