228 lines
8.5 KiB
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>
|