store/resources/js/components/admin/system/roles/RoleForm.vue

228 lines
8.5 KiB
Vue

<script setup lang="ts">
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Field,
FieldError,
FieldGroup,
FieldLabel,
FieldSet,
} from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { formErrors } from '@/lib/form';
import { useForm } from '@inertiajs/vue3';
import { Save } from '@lucide/vue';
import { computed } from 'vue';
import { toast } from 'vue-sonner';
interface PermissionOption {
value: string;
label: string;
group: string;
}
const props = withDefaults(
defineProps<{
permissions: PermissionOption[];
initialData?: {
name?: string;
permission_names?: string[];
};
submitUrl: string;
method?: 'post' | 'put';
submitLabel?: string;
isProtected?: boolean;
}>(),
{
method: 'post',
submitLabel: 'Simpan',
isProtected: false,
},
);
const form = useForm({
name: props.initialData?.name ?? '',
permissions: props.initialData?.permission_names ?? [],
});
// Group permissions by group/module
const groupedPermissions = computed(() => {
const groups: Record<string, PermissionOption[]> = {};
props.permissions.forEach((perm) => {
if (!groups[perm.group]) {
groups[perm.group] = [];
}
groups[perm.group].push(perm);
});
return groups;
});
function togglePermission(permValue: string, checked: boolean) {
if (checked) {
if (!form.permissions.includes(permValue)) {
form.permissions = [...form.permissions, permValue];
}
} else {
form.permissions = form.permissions.filter((p) => p !== permValue);
}
}
function isPermissionChecked(permValue: string): boolean {
return form.permissions.includes(permValue);
}
// Check/Uncheck all for a specific module group
function isGroupAllChecked(groupName: string): boolean {
const groupPerms = groupedPermissions.value[groupName] || [];
if (groupPerms.length === 0) return false;
return groupPerms.every((perm) => form.permissions.includes(perm.value));
}
function toggleGroupAll(groupName: string, checked: boolean) {
const groupPerms = groupedPermissions.value[groupName] || [];
const groupValues = groupPerms.map((p) => p.value);
if (checked) {
// Add all group permissions that are not already in form.permissions
const toAdd = groupValues.filter((v) => !form.permissions.includes(v));
form.permissions = [...form.permissions, ...toAdd];
} else {
// Remove all group permissions
form.permissions = form.permissions.filter(
(p) => !groupValues.includes(p),
);
}
}
function submit() {
form[props.method](props.submitUrl, {
onSuccess: () => {
toast.success(
`Role berhasil ${props.method === 'post' ? 'dibuat' : 'diperbarui'}.`,
);
},
onError: () => {
toast.error('Gagal menyimpan data. Periksa kembali formulir.');
},
});
}
</script>
<template>
<form @submit.prevent="submit">
<div class="grid gap-6">
<Card>
<CardHeader>
<CardTitle>Informasi Role</CardTitle>
</CardHeader>
<CardContent>
<FieldGroup>
<FieldSet class="grid gap-4">
<Field>
<FieldLabel for="name" required
>Nama</FieldLabel
>
<Input
id="name"
v-model="form.name"
type="text"
placeholder="Contoh: admin-gudang"
:disabled="isProtected"
/>
<span
v-if="isProtected"
class="mt-1 text-xs text-muted-foreground"
>
Nama bawaan sistem (developer/owner) tidak
dapat diubah.
</span>
<FieldError
:errors="formErrors(form, 'name')"
/>
</Field>
</FieldSet>
</FieldGroup>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Hak Akses (Permissions)</CardTitle>
</CardHeader>
<CardContent>
<div class="space-y-6">
<div
v-for="(perms, groupName) in groupedPermissions"
:key="groupName"
class="border-b pb-6 last:border-b-0 last:pb-0"
>
<div class="mb-3 flex items-center justify-between">
<h3
class="text-base font-semibold text-foreground"
>
{{ groupName }}
</h3>
<label
class="flex cursor-pointer items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground"
>
<input
type="checkbox"
class="size-3.5 cursor-pointer rounded border-input"
:checked="isGroupAllChecked(groupName)"
@change="
toggleGroupAll(
groupName,
(
$event.target as HTMLInputElement
).checked,
)
"
/>
<span>Pilih Semua</span>
</label>
</div>
<div
class="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4"
>
<label
v-for="perm in perms"
:key="perm.value"
class="flex cursor-pointer items-center gap-2 rounded-md border px-3 py-2 transition-colors hover:bg-muted/50"
>
<input
type="checkbox"
class="size-4 cursor-pointer rounded border-input"
:checked="
isPermissionChecked(perm.value)
"
@change="
togglePermission(
perm.value,
(
$event.target as HTMLInputElement
).checked,
)
"
/>
<span class="text-sm">{{
perm.label
}}</span>
</label>
</div>
</div>
<FieldError :errors="formErrors(form, 'permissions')" />
</div>
</CardContent>
</Card>
<div class="flex items-center justify-end">
<Button type="submit" :disabled="form.processing">
<Save class="size-4" />
{{ form.processing ? 'Menyimpan...' : submitLabel }}
</Button>
</div>
</div>
</form>
</template>