Add source_site_id to ActiveSite model and implement unique constraint. Update serializers and views for site registration and claiming. Enhance tests for site registration and claiming functionality.

This commit is contained in:
Alberto-Audrix committed 2026-09-15 09:45:23 +07:00
1 parent 521f36d22d
commit 61f954a3bd
12 files changed
+684 -172

No files matched your search

+8 -31
View File
@@ -221,9 +221,7 @@ const GmViewList: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const [notice, setNotice] = useState<string | null>(null);
const [name, setName] = useState('');
const [code, setCode] = useState('');
const [codeTouched, setCodeTouched] = useState(false);
const flash = (msg: string) => {
setNotice(msg);
@@ -258,9 +256,7 @@ const GmViewList: React.FC = () => {
}, [reload]);
const resetForm = () => {
setName('');
setCode('');
setCodeTouched(false);
};
const wrap = async (fn: () => Promise<void>) => {
@@ -310,7 +306,7 @@ const GmViewList: React.FC = () => {
<div>
<PageHeader
title="Pengaturan"
subtitle="Pilih BUH Anda, lalu daftarkan site dengan nama dan kode"
subtitle="Pilih BUH Anda, lalu klaim site dengan kode dari dasbor site"
/>
{error && (
<div className="mb-4">
@@ -396,57 +392,38 @@ const GmViewList: React.FC = () => {
<section className="bg-white rounded-xl shadow-sm p-5">
<h2 className="font-semibold text-gray-800 mb-1">Tambah site</h2>
<p className="text-xs text-gray-500 mb-4">
Masukkan nama dan kode yang sama dengan yang diisi manajer di dasbor site, agar site
terhubung ke daftar tampilan Anda.
Masukkan kode yang sama dengan yang dipakai staf saat daftar ke pusat di dasbor site.
Site harus sudah mendaftar dulu sebelum bisa masuk daftar tampilan Anda.
</p>
<form
className="space-y-3"
onSubmit={(e) => {
e.preventDefault();
const resolvedCode = code || slugify(name);
const resolvedCode = slugify(code);
if (!resolvedCode) return;
void wrap(async () => {
const created = await api.activeSites.create({
const claimed = await api.activeSites.create({
code: resolvedCode,
name: name.trim(),
is_active: true,
});
resetForm();
flash(`Site ${created.name} ditambahkan ke daftar tampilan.`);
flash(`Site ${claimed.name} ditambahkan ke daftar tampilan.`);
});
}}
>
<label className="block text-sm text-gray-700">
Nama site
<input
className={`${inputClass} mt-1`}
placeholder="Contoh: Sukawarna"
value={name}
onChange={(e) => {
const next = e.target.value;
setName(next);
if (!codeTouched) setCode(slugify(next));
}}
required
/>
</label>
<label className="block text-sm text-gray-700">
Kode
<input
className={`${inputClass} mt-1`}
placeholder="sukawarna"
value={code}
onChange={(e) => {
setCodeTouched(true);
setCode(slugify(e.target.value));
}}
onChange={(e) => setCode(slugify(e.target.value))}
required
/>
</label>
<button
type="submit"
className="bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-semibold disabled:opacity-50"
disabled={saving}
disabled={saving || !code}
>
{saving ? 'Menyimpan...' : 'Tambah ke daftar'}
</button>
+2 -2
View File
@@ -87,7 +87,7 @@ const UserManagementPage: React.FC = () => {
password,
});
setNotice(
`Akun "${userName.trim()}" dibuat. Bagikan password sementara secara aman (di luar sistem).`
`Akun "${userName.trim()}" dibuat. Jangan lupa ganti password setelah login pertama kali.`
);
setUserLogin('');
setUserName('');
@@ -133,7 +133,7 @@ const UserManagementPage: React.FC = () => {
setBusyId(id);
try {
await api.users.setPassword(id, resetPassword);
setNotice('Password direset. Bagikan password baru secara aman (di luar sistem).');
setNotice('Password direset. Bagikan password baru secara aman.');
setResetId(null);
setResetPassword('');
await reload();
+2 -6
View File
@@ -156,7 +156,7 @@ describe('SettingsPage city site registry', () => {
render(<SettingsPage />);
await screen.findByText('Sukawarna');
fireEvent.change(screen.getByPlaceholderText('Contoh: Sukawarna'), {
fireEvent.change(screen.getByPlaceholderText('sukawarna'), {
target: { value: 'Cikupa' },
});
fireEvent.click(screen.getByRole('button', { name: 'Tambah ke daftar' }));
@@ -164,12 +164,8 @@ describe('SettingsPage city site registry', () => {
await waitFor(() => expect(createMock).toHaveBeenCalledTimes(1));
expect(createMock).toHaveBeenCalledWith({
code: 'cikupa',
name: 'Cikupa',
is_active: true,
});
});
it('lets GM request a BUH', async () => {
}); it('lets GM request a BUH', async () => {
requestMock.mockResolvedValue({
...authState.user,
managed_by_pending: 2,