Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions frontend/src/services/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const ROLE_PERMISSIONS: Record<UserRole, UserPermissions> = {
STAFF: { temperatureLogging: true, checklists: true, reports: false, deviations: true, userAdmin: false, settings: false },
}

interface BackendLocation {
id: number
name: string
address: string
}

interface BackendUser {
id: number
firstName: string
Expand Down Expand Up @@ -76,17 +82,22 @@ export const organizationService = {
}))
},

async updateUser(id: number, data: Partial<SettingsUser>): Promise<SettingsUser> {
async getLocations(): Promise<BackendLocation[]> {
const res = await api.get<BackendLocation[]>('/locations')
return res.data
},

async updateUser(id: number, data: Partial<SettingsUser> & { locationId?: number }): Promise<SettingsUser> {
// Name update
if (data.firstName !== undefined || data.lastName !== undefined) {
await api.put(`/users/${id}`, {
firstName: data.firstName,
lastName: data.lastName,
})
}
// Role update (separate endpoint)
// Role update (separate endpoint) — locationId required for MANAGER/STAFF
if (data.role !== undefined) {
await api.put(`/users/${id}/role`, { role: data.role })
await api.put(`/users/${id}/role`, { role: data.role, locationId: data.locationId ?? null })
}
// Re-fetch the updated user from the list
const users = await this.getUsers()
Expand All @@ -95,12 +106,13 @@ export const organizationService = {
return updated
},

async createUser(data: Omit<SettingsUser, 'id'>): Promise<SettingsUser> {
async createUser(data: Omit<SettingsUser, 'id'> & { locationId?: number | null }): Promise<SettingsUser> {
const res = await api.post<BackendUser>('/users', {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
role: data.role,
locationId: data.locationId ?? null,
})
return {
id: res.data.id,
Expand Down
33 changes: 29 additions & 4 deletions frontend/src/views/settings/UsersTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@
</div>
</div>

<div
v-if="(form.role === 'MANAGER' || form.role === 'STAFF') && form.role !== originalRole"
class="form-group mt-3"
>
<label class="font-medium text-sm">Lokasjon</label>
<select v-model="form.locationId" class="form-control">
<option :value="null" disabled>Velg lokasjon</option>
<option v-for="loc in locations" :key="loc.id" :value="loc.id">{{ loc.name }}</option>
</select>
</div>

<div class="divider mt-4 mb-3" />
<p class="font-semibold text-sm mb-3">Tillatelser</p>

Expand Down Expand Up @@ -139,20 +150,22 @@ import { organizationService } from '@/services/organization.service'
import type { SettingsUser, UserRole } from '@/types'

const users = ref<SettingsUser[]>([])
const locations = ref<{ id: number; name: string }[]>([])
const loading = ref(false)
const showModal = ref(false)
const isEditing = ref(false)
const editingId = ref<number | null>(null)
const originalRole = ref<UserRole | null>(null)
const saveError = ref<string | null>(null)

type FormData = Omit<SettingsUser, 'id' | 'colorBg' | 'colorText'>
type FormData = Omit<SettingsUser, 'id' | 'colorBg' | 'colorText'> & { locationId: number | null }

const emptyForm = (): FormData => ({
firstName: '',
lastName: '',
email: '',
role: 'STAFF',
locationId: null,
isActive: true,
permissions: {
temperatureLogging: false,
Expand All @@ -169,7 +182,10 @@ const form = reactive<FormData>(emptyForm())
async function fetchUsers() {
loading.value = true
try {
users.value = await organizationService.getUsers()
;[users.value, locations.value] = await Promise.all([
organizationService.getUsers(),
organizationService.getLocations(),
])
} finally {
loading.value = false
}
Expand Down Expand Up @@ -207,6 +223,7 @@ function openAddModal() {
isEditing.value = false
editingId.value = null
saveError.value = null
originalRole.value = null
Object.assign(form, emptyForm())
showModal.value = true
}
Expand All @@ -223,20 +240,28 @@ function openEditModal(user: SettingsUser) {
role: user.role,
isActive: user.isActive,
permissions: { ...user.permissions },
locationId: null,
})
showModal.value = true
}

async function save() {
saveError.value = null
const needsLocation = form.role === 'MANAGER' || form.role === 'STAFF'
const roleChanged = form.role !== originalRole.value
if (needsLocation && (roleChanged || !isEditing.value) && !form.locationId) {
saveError.value = 'Velg en lokasjon for denne rollen.'
return
}

const colors = getAvatarColors(form.role)
const userData: Partial<SettingsUser> & { colorBg: string; colorText: string } = {
const userData: Partial<SettingsUser> & { colorBg: string; colorText: string; locationId?: number | null } = {
...form,
colorBg: colors.bg,
colorText: colors.text,
}
// Only send role if it actually changed — role endpoint returns 403 otherwise
if (isEditing.value && form.role === originalRole.value) {
if (isEditing.value && !roleChanged) {
delete userData.role
}

Expand Down
Loading