Collect user_login and user_name in account admin UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alberto-AudrixandCursor committed 2026-09-08 16:30:14 +07:00
1 parent 92be5ee3d2
commit 7a562c18ec
4 files changed
+43 -11

No files matched your search

+25 -8
View File
@@ -39,6 +39,7 @@ const UserManagementPage: React.FC = () => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [notice, setNotice] = useState<string | null>(null); const [notice, setNotice] = useState<string | null>(null);
const [userLogin, setUserLogin] = useState('');
const [userName, setUserName] = useState(''); const [userName, setUserName] = useState('');
const [status, setStatus] = useState<UserStatus>('gm'); const [status, setStatus] = useState<UserStatus>('gm');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
@@ -80,6 +81,7 @@ const UserManagementPage: React.FC = () => {
setSaving(true); setSaving(true);
try { try {
await api.users.create({ await api.users.create({
user_login: userLogin.trim(),
user_name: userName.trim(), user_name: userName.trim(),
status, status,
password, password,
@@ -87,6 +89,7 @@ const UserManagementPage: React.FC = () => {
setNotice( setNotice(
`Akun "${userName.trim()}" dibuat. Bagikan password sementara secara aman (di luar sistem).` `Akun "${userName.trim()}" dibuat. Bagikan password sementara secara aman (di luar sistem).`
); );
setUserLogin('');
setUserName(''); setUserName('');
setPassword(''); setPassword('');
setStatus('gm'); setStatus('gm');
@@ -151,9 +154,20 @@ const UserManagementPage: React.FC = () => {
<section className="bg-white rounded-xl shadow-sm p-5 space-y-4"> <section className="bg-white rounded-xl shadow-sm p-5 space-y-4">
<h2 className="text-base font-semibold text-gray-900">Buat pengguna baru</h2> <h2 className="text-base font-semibold text-gray-900">Buat pengguna baru</h2>
<form onSubmit={onCreate} className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> <form onSubmit={onCreate} className="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
<label className="block text-sm"> <label className="block text-sm">
<span className="text-gray-600">Username</span> <span className="text-gray-600">Login</span>
<input
className={inputClass}
value={userLogin}
onChange={(e) => setUserLogin(e.target.value)}
required
maxLength={30}
autoComplete="off"
/>
</label>
<label className="block text-sm">
<span className="text-gray-600">Nama</span>
<input <input
className={inputClass} className={inputClass}
value={userName} value={userName}
@@ -214,7 +228,7 @@ const UserManagementPage: React.FC = () => {
<table className="min-w-full text-sm"> <table className="min-w-full text-sm">
<thead> <thead>
<tr className="text-left text-gray-500 border-b"> <tr className="text-left text-gray-500 border-b">
<th className="py-2 pr-4 font-medium">Username</th> <th className="py-2 pr-4 font-medium">Nama</th>
<th className="py-2 pr-4 font-medium">Role</th> <th className="py-2 pr-4 font-medium">Role</th>
<th className="py-2 pr-4 font-medium">Managed by</th> <th className="py-2 pr-4 font-medium">Managed by</th>
<th className="py-2 font-medium">Aksi</th> <th className="py-2 font-medium">Aksi</th>
@@ -225,11 +239,14 @@ const UserManagementPage: React.FC = () => {
const isSelf = currentUser?.id === user.id; const isSelf = currentUser?.id === user.id;
return ( return (
<tr key={user.id} className="border-b border-gray-100 align-top"> <tr key={user.id} className="border-b border-gray-100 align-top">
<td className="py-3 pr-4 font-medium text-gray-900"> <td className="py-3 pr-4">
{user.user_name} <div className="font-medium text-gray-900">
{isSelf ? ( {user.user_name}
<span className="ml-2 text-xs font-normal text-gray-500">(Anda)</span> {isSelf ? (
) : null} <span className="ml-2 text-xs font-normal text-gray-500">(Anda)</span>
) : null}
</div>
<div className="text-xs text-gray-500">{user.user_login}</div>
</td> </td>
<td className="py-3 pr-4"> <td className="py-3 pr-4">
{!isSelf && editId === user.id ? ( {!isSelf && editId === user.id ? (
@@ -6,6 +6,7 @@ vi.mock('../../context/AuthContext.tsx', () => ({
useAuth: () => ({ useAuth: () => ({
user: { user: {
id: 9, id: 9,
user_login: 'account_admin',
user_name: 'account_admin', user_name: 'account_admin',
status: 'account_admin', status: 'account_admin',
is_superuser: false, is_superuser: false,
+6 -1
View File
@@ -26,6 +26,7 @@ const {
authState: { authState: {
user: { user: {
id: 5, id: 5,
user_login: 'gm.user',
user_name: 'gm.user', user_name: 'gm.user',
status: 'gm' as string, status: 'gm' as string,
managed_by: null as number | null, managed_by: null as number | null,
@@ -84,6 +85,7 @@ describe('SettingsPage city site registry', () => {
refreshUserMock.mockReset(); refreshUserMock.mockReset();
authState.user = { authState.user = {
id: 5, id: 5,
user_login: 'gm.user',
user_name: 'gm.user', user_name: 'gm.user',
status: 'gm', status: 'gm',
managed_by: null, managed_by: null,
@@ -127,11 +129,12 @@ describe('SettingsPage city site registry', () => {
updated_at: '', updated_at: '',
}); });
buhsMock.mockResolvedValue([ buhsMock.mockResolvedValue([
{ id: 2, user_name: 'buh', status: 'buh', created_at: '', updated_at: '' }, { id: 2, user_login: 'buh', user_name: 'buh', status: 'buh', created_at: '', updated_at: '' },
]); ]);
usersListMock.mockResolvedValue([ usersListMock.mockResolvedValue([
{ {
id: 5, id: 5,
user_login: 'gm',
user_name: 'gm', user_name: 'gm',
status: 'gm', status: 'gm',
managed_by: 2, managed_by: 2,
@@ -188,6 +191,7 @@ describe('SettingsPage city site registry', () => {
it('shows pending GM confirmation for BUH', async () => { it('shows pending GM confirmation for BUH', async () => {
authState.user = { authState.user = {
id: 2, id: 2,
user_login: 'buh',
user_name: 'buh', user_name: 'buh',
status: 'buh', status: 'buh',
managed_by: null, managed_by: null,
@@ -200,6 +204,7 @@ describe('SettingsPage city site registry', () => {
pendingMock.mockResolvedValue([ pendingMock.mockResolvedValue([
{ {
id: 5, id: 5,
user_login: 'gm.user',
user_name: 'gm.user', user_name: 'gm.user',
status: 'gm', status: 'gm',
created_at: '', created_at: '',
@@ -10,6 +10,7 @@ const { listMock, createMock, updateMock, setPasswordMock, authState } = vi.hois
authState: { authState: {
user: { user: {
id: 99, id: 99,
user_login: 'account_admin',
user_name: 'account_admin', user_name: 'account_admin',
status: 'account_admin' as string, status: 'account_admin' as string,
created_at: '', created_at: '',
@@ -44,6 +45,7 @@ describe('UserManagementPage', () => {
setPasswordMock.mockReset(); setPasswordMock.mockReset();
authState.user = { authState.user = {
id: 99, id: 99,
user_login: 'account_admin',
user_name: 'account_admin', user_name: 'account_admin',
status: 'account_admin', status: 'account_admin',
created_at: '', created_at: '',
@@ -52,6 +54,7 @@ describe('UserManagementPage', () => {
listMock.mockResolvedValue([ listMock.mockResolvedValue([
{ {
id: 1, id: 1,
user_login: 'gm1.login',
user_name: 'gm1', user_name: 'gm1',
status: 'gm', status: 'gm',
managed_by_name: 'buh1', managed_by_name: 'buh1',
@@ -60,6 +63,7 @@ describe('UserManagementPage', () => {
}, },
{ {
id: 99, id: 99,
user_login: 'account_admin',
user_name: 'account_admin', user_name: 'account_admin',
status: 'account_admin', status: 'account_admin',
managed_by_name: null, managed_by_name: null,
@@ -69,6 +73,7 @@ describe('UserManagementPage', () => {
]); ]);
createMock.mockResolvedValue({ createMock.mockResolvedValue({
id: 2, id: 2,
user_login: 'newgm',
user_name: 'newgm', user_name: 'newgm',
status: 'gm', status: 'gm',
created_at: '', created_at: '',
@@ -90,9 +95,12 @@ describe('UserManagementPage', () => {
render(<UserManagementPage />); render(<UserManagementPage />);
await screen.findByText('gm1'); await screen.findByText('gm1');
fireEvent.change(screen.getByLabelText(/Username/i), { fireEvent.change(screen.getByLabelText(/^Login$/i), {
target: { value: 'newgm' }, target: { value: 'newgm' },
}); });
fireEvent.change(screen.getByLabelText(/^Nama$/i), {
target: { value: 'New GM' },
});
fireEvent.change(screen.getByLabelText(/Password sementara/i), { fireEvent.change(screen.getByLabelText(/Password sementara/i), {
target: { value: 'temp123' }, target: { value: 'temp123' },
}); });
@@ -100,7 +108,8 @@ describe('UserManagementPage', () => {
await waitFor(() => { await waitFor(() => {
expect(createMock).toHaveBeenCalledWith({ expect(createMock).toHaveBeenCalledWith({
user_name: 'newgm', user_login: 'newgm',
user_name: 'New GM',
status: 'gm', status: 'gm',
password: 'temp123', password: 'temp123',
}); });