feat: connect UsersPage and SettingsPage SMTP to API + extend user roles/fields

- Migration 034: extend users table with phone, position, active columns; add new roles (receptionist, accountant, security)
- Backend users.ts: accept phone/position on create, accept phone/position/active/role on update; managers can now assign all non-admin roles
- types/index.ts + api.ts: add phone, position, active to User type and users create/update payload types
- UsersPage: fix mapRole to handle all 5 roles, fix toStaffUser to use real phone/position/active from API, fix handleSave to pass all fields
- SettingsPage: load SMTP settings from hotelSettings API on mount, save SMTP on handleSave when section === 'notify'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 00:29:13 +03:00
parent c610486d08
commit 943b97c200
6 changed files with 80 additions and 17 deletions

View File

@@ -264,10 +264,10 @@ export const api = {
list: (slug: string) =>
req<User[]>('GET', `/api/hotels/${slug}/users`),
create: (slug: string, data: { email: string; password: string; name: string; role: string }) =>
create: (slug: string, data: { email: string; password: string; name: string; role: string; phone?: string; position?: string }) =>
req<User>('POST', `/api/hotels/${slug}/users`, data),
update: (slug: string, id: string, data: Partial<{ name: string; email: string; password: string }>) =>
update: (slug: string, id: string, data: Partial<{ name: string; email: string; password: string; role: string; phone: string; position: string; active: boolean }>) =>
req<User>('PATCH', `/api/hotels/${slug}/users/${id}`, data),
delete: (slug: string, id: string) =>

View File

@@ -97,6 +97,14 @@ export function SettingsPage() {
if (s.auto_cancel_noshow_hours) setAutoCancelNoShowHours(Number(s.auto_cancel_noshow_hours))
setAutoCheckout(Boolean(s.auto_checkout_enabled))
if (s.auto_checkout_hours) setAutoCheckoutHours(Number(s.auto_checkout_hours))
setSmtp({
host: String(s.smtp_host ?? ''),
port: String(s.smtp_port ?? '587'),
user: String(s.smtp_user ?? ''),
password: String(s.smtp_password ?? ''),
fromEmail: String(s.smtp_from_email ?? ''),
fromName: String(s.smtp_from_name ?? ''),
})
setHotelSettingsLoaded(true)
})
.catch(console.error)
@@ -249,6 +257,20 @@ export function SettingsPage() {
console.error('Failed to save hotel settings', err)
}
}
if (section === 'notify') {
try {
await api.hotelSettings.update(slug, {
smtp_host: smtp.host,
smtp_port: smtp.port,
smtp_user: smtp.user,
smtp_password: smtp.password,
smtp_from_email: smtp.fromEmail,
smtp_from_name: smtp.fromName,
})
} catch (err) {
console.error('Failed to save SMTP settings', err)
}
}
setSaved(true)
setTimeout(() => setSaved(false), 2000)
}

View File

@@ -174,7 +174,11 @@ const INITIAL_ROLE_PERMISSIONS: RolePermissions[] = [
// ── Helpers ────────────────────────────────────────────────────────────────────
function mapRole(r: string): StaffRole {
return r === 'housekeeper' ? 'housekeeper' : 'hotel_manager'
if (r === 'housekeeper') return 'housekeeper'
if (r === 'receptionist') return 'receptionist'
if (r === 'accountant') return 'accountant'
if (r === 'security') return 'security'
return 'hotel_manager'
}
function toStaffUser(u: User): StaffUser {
@@ -188,9 +192,10 @@ function toStaffUser(u: User): StaffUser {
firstName,
lastName,
email: u.email,
phone: u.phone ?? '',
role,
position: DEFAULT_POSITIONS[role]?.[0] ?? '',
isActive: true,
position: u.position ?? DEFAULT_POSITIONS[role]?.[0] ?? '',
isActive: u.active ?? true,
createdAt: u.createdAt?.slice(0, 10) ?? '',
avatarColor: AVATAR_COLORS[colorIndex],
}
@@ -701,15 +706,23 @@ export function UsersPage() {
const handleSave = async (u: StaffUser, password?: string) => {
const fullName = `${u.firstName} ${u.lastName}`.trim()
const backendRole = u.role === 'housekeeper' ? 'housekeeper' : 'manager'
const backendRole = u.role === 'housekeeper' ? 'housekeeper'
: u.role === 'receptionist' ? 'receptionist'
: u.role === 'accountant' ? 'accountant'
: u.role === 'security' ? 'security'
: 'manager'
try {
if (!u.id) {
const created = await api.users.create(slug, {
email: u.email, name: fullName, password: password ?? '', role: backendRole,
phone: u.phone || undefined, position: u.position || undefined,
})
setUsers(prev => [...prev, toStaffUser(created)])
} else {
const upd: Partial<{ name: string; email: string; password: string }> = { name: fullName, email: u.email }
const upd: Partial<{ name: string; email: string; password: string; role: string; phone: string; position: string; active: boolean }> = {
name: fullName, email: u.email, role: backendRole,
phone: u.phone || undefined, position: u.position || undefined, active: u.isActive,
}
if (password) upd.password = password
const updated = await api.users.update(slug, u.id, upd)
setUsers(prev => prev.map(x => x.id === u.id ? { ...toStaffUser(updated), lastLogin: x.lastLogin } : x))

View File

@@ -11,6 +11,9 @@ export interface User {
hotelName?: string
hotelSlug?: string
avatarUrl?: string
phone?: string | null
position?: string | null
active?: boolean
createdAt?: string
updatedAt?: string
}