feat: Update manager permissions and enhance toast notifications with internationalization support
This commit is contained in:
@@ -61,7 +61,7 @@ export default function(db) {
|
||||
});
|
||||
|
||||
// Definitive version using a dedicated database connection
|
||||
router.post('/enabled-dates/update', checkPermission('view_all'), async (req, res) => {
|
||||
router.post('/enabled-dates/update', checkPermission('manage_resources'), async (req, res) => {
|
||||
let connection; // Define connection here to ensure it's accessible in the 'finally' block
|
||||
try {
|
||||
const { datesToEnable, datesToDisable } = req.body;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="min-h-screen bg-gray-100 text-gray-900 dark:bg-gray-900 dark:text-gray-100 transition-colors duration-300">
|
||||
<div class="fixed bottom-4 right-4 space-y-2 z-50">
|
||||
<div class="fixed bottom-4 right-4 space-y-2 z-50 z-9999">
|
||||
<component v-for="toast in renderToasts()" :is="toast" :key="toast.key" />
|
||||
</div>
|
||||
<header
|
||||
|
||||
@@ -198,9 +198,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { ref, onMounted, computed, watch, nextTick } from 'vue';
|
||||
import { apiFetch } from '@/api.js';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const managers = ref([]);
|
||||
const loading = ref(false);
|
||||
@@ -229,6 +230,7 @@ const newManager = ref({
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const { t: $t } = useI18n();
|
||||
|
||||
const isManagerFormValid = computed(() =>
|
||||
newManager.value.fullName &&
|
||||
@@ -243,6 +245,8 @@ const permissionsList = ref([
|
||||
{ key: 'manager_permissions', label: 'Manage Managers (Add/Edit/Delete & Permissions)' },
|
||||
]);
|
||||
|
||||
const initialLoad = ref(true);
|
||||
|
||||
const totalPages = computed(() => {
|
||||
const pages = Math.ceil(totalManagers.value / pageSize.value);
|
||||
return pages < 1 ? 1 : pages;
|
||||
@@ -274,7 +278,7 @@ const fetchManagers = async (page = currentPage.value) => {
|
||||
errorMessage.value = 'Failed to fetch managers.';
|
||||
managers.value = [];
|
||||
totalManagers.value = 0;
|
||||
toast.showToast('Failed to fetch managers.', 'error');
|
||||
toast.showToast($t('fetchManagersFailed'), 'error');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -296,8 +300,12 @@ const jumpToPage = () => {
|
||||
};
|
||||
|
||||
const openSettingsModal = (manager) => {
|
||||
initialLoad.value = true;
|
||||
editingManager.value = { ...manager };
|
||||
isSettingsModalVisible.value = true;
|
||||
nextTick(() => {
|
||||
initialLoad.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const closeSettingsModal = () => {
|
||||
@@ -315,49 +323,69 @@ const saveManagerSettings = async () => {
|
||||
saving.value = true;
|
||||
passwordErrorMessage.value = '';
|
||||
passwordSuccessMessage.value = '';
|
||||
console.log('Starting saveManagerSettings...');
|
||||
|
||||
try {
|
||||
// Save permissions - convert to new structure
|
||||
const permissionsToSave = {
|
||||
// Save all changes in parallel for better performance
|
||||
const results = await Promise.all([
|
||||
// Save permissions
|
||||
apiFetch(`/api/managers/permissions/${editingManager.value.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
view_all: editingManager.value.view_all || false,
|
||||
edit_workers: editingManager.value.edit_workers || false,
|
||||
manage_resources: editingManager.value.manage_resources || false,
|
||||
manager_permissions: editingManager.value.manager_permissions || false
|
||||
};
|
||||
const permissionsResponse = await apiFetch(`/api/managers/permissions/${editingManager.value.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(permissionsToSave),
|
||||
});
|
||||
|
||||
if (!permissionsResponse.success) {
|
||||
throw new Error(permissionsResponse.message || 'Failed to update permissions.');
|
||||
}
|
||||
|
||||
}),
|
||||
}),
|
||||
// Save manager details
|
||||
await apiFetch(`/api/managers/managers/${editingManager.value.id}`, {
|
||||
apiFetch(`/api/managers/managers/${editingManager.value.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
department: editingManager.value.department,
|
||||
position: editingManager.value.position,
|
||||
status: editingManager.value.isActive ? 'active' : 'inactive',
|
||||
}),
|
||||
});
|
||||
|
||||
}),
|
||||
// Save password if changed
|
||||
if (newPassword.value) {
|
||||
if (newPassword.value !== confirmNewPassword.value) {
|
||||
throw new Error('Passwords do not match.');
|
||||
}
|
||||
await apiFetch(`/api/managers/managers/${editingManager.value.id}/password`, {
|
||||
newPassword.value && newPassword.value === confirmNewPassword.value
|
||||
? apiFetch(`/api/managers/managers/${editingManager.value.id}/password`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ newPassword: newPassword.value }),
|
||||
});
|
||||
})
|
||||
: Promise.resolve()
|
||||
]);
|
||||
const [permissionsResponse] = results;
|
||||
|
||||
console.log('API responses:', results);
|
||||
// Check for explicit error responses
|
||||
if (permissionsResponse && permissionsResponse.error) {
|
||||
console.log('Permissions update failed:', permissionsResponse.error);
|
||||
throw new Error(permissionsResponse.error || 'Failed to update permissions.');
|
||||
}
|
||||
|
||||
if (newPassword.value && newPassword.value !== confirmNewPassword.value) {
|
||||
console.log('Password mismatch detected');
|
||||
throw new Error('Passwords do not match.');
|
||||
}
|
||||
|
||||
// Refresh data
|
||||
await fetchManagers(currentPage.value);
|
||||
closeSettingsModal();
|
||||
toast.showToast('Manager settings saved successfully!', 'success');
|
||||
toast.showToast($t('managerSettingsSaved'), 'success');
|
||||
|
||||
// Wait for toast to appear before closing modal
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Reset form and close modal - ensure all state is cleared
|
||||
editingManager.value = null;
|
||||
newPassword.value = '';
|
||||
confirmNewPassword.value = '';
|
||||
passwordErrorMessage.value = '';
|
||||
passwordSuccessMessage.value = '';
|
||||
showDeleteConfirm.value = false;
|
||||
isSettingsModalVisible.value = false;
|
||||
} catch (err) {
|
||||
console.error('Error in saveManagerSettings:', err);
|
||||
passwordErrorMessage.value = err.message || 'Failed to save settings.';
|
||||
toast.showToast(err.message || 'Failed to save settings.', 'error');
|
||||
} finally {
|
||||
@@ -413,10 +441,9 @@ const closeAddManagerModal = () => {
|
||||
|
||||
await fetchManagers(1); // Auto-refetch the manager list after adding
|
||||
closeAddManagerModal(); // Auto-close the modal after successful addition
|
||||
toast.showToast('Manager added successfully!', 'success');
|
||||
} catch (err) {
|
||||
const displayMessage = err.message || err.sqlMessage || 'Error adding manager';
|
||||
toast.showToast(displayMessage, 'error');
|
||||
toast.showToast($t('managerAddedSuccess'), 'success');
|
||||
} catch (_err) {
|
||||
toast.showToast($t('addManagerError'), 'error');
|
||||
} finally {
|
||||
addingManager.value = false;
|
||||
}
|
||||
@@ -425,12 +452,12 @@ const closeAddManagerModal = () => {
|
||||
const deleteManager = async (id) => {
|
||||
try {
|
||||
await apiFetch(`/api/managers/managers/${id}`, { method: 'DELETE' });
|
||||
toast.showToast('Manager Deleted successfully.', 'success');
|
||||
toast.showToast($t('managerDeletedSuccess'), 'success');
|
||||
// Adjust page number if the last manager on a page was deleted
|
||||
fetchManagers(managers.value.length === 1 && currentPage.value > 1 ? currentPage.value - 1 : currentPage.value);
|
||||
closeSettingsModal();
|
||||
} catch (_err) {
|
||||
toast.showToast('Failed to delete manager.', 'error');
|
||||
toast.showToast($t('deleteManagerFailed'), 'error');
|
||||
errorMessage.value = 'Failed to Delete manager.'; // This could also be removed if toast is sufficient
|
||||
}
|
||||
};
|
||||
|
||||
@@ -340,9 +340,9 @@ const addWorker = async () => {
|
||||
});
|
||||
await fetchWorkers(1);
|
||||
newWorker.value = { fullName: '', username: '', password: '', department: '', position: '' };
|
||||
toast.showToast('Worker added successfully', 'success');
|
||||
toast.showToast($t('workerAdded'), 'success');
|
||||
} catch (_err) {
|
||||
toast.showToast(_err.message || 'Error adding user.', 'error');
|
||||
toast.showToast(_err.message || $t('addUserError'), 'error');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -354,7 +354,7 @@ const deleteWorker = async (id) => {
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
await apiFetch(`/api/managers/workers/${id}`, { method: 'DELETE' });
|
||||
toast.showToast('Worker soft-deleted successfully.', 'success');
|
||||
toast.showToast($t('workerSoftDeleted'), 'success');
|
||||
fetchWorkers(workers.value.length === 1 && currentPage.value > 1 ? currentPage.value - 1 : currentPage.value);
|
||||
} catch (_err) {
|
||||
errorMessage.value = 'Failed to soft-delete worker.';
|
||||
@@ -365,9 +365,9 @@ const clearDevice = async (workerId) => {
|
||||
const toast = useToast();
|
||||
try {
|
||||
await apiFetch(`/api/managers/workers/${workerId}/reset-device`, { method: 'PUT' });
|
||||
toast.showToast('Worker device cleared successfully.', 'success');
|
||||
toast.showToast($t('deviceCleared'), 'success');
|
||||
} catch (_err) {
|
||||
toast.showToast(_err.message || 'Failed to clear device.', 'error');
|
||||
toast.showToast(_err.message || $t('clearDeviceFailed'), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -378,7 +378,7 @@ const saveWorkerSettings = async () => {
|
||||
passwordSuccessMessage.value = '';
|
||||
let passwordUpdated = false;
|
||||
let detailsUpdated = false;
|
||||
toast.showToast('Saving settings...', 'info');
|
||||
toast.showToast($t('savingSettings'), 'info');
|
||||
|
||||
// Handle password change
|
||||
if (newPassword.value || confirmNewPassword.value) {
|
||||
@@ -495,7 +495,7 @@ const toggleSelectAll = (event) => {
|
||||
const exportWorkHours = async () => {
|
||||
const toast = useToast();
|
||||
exportLoading.value = true;
|
||||
toast.showToast('Exporting records...', 'info');
|
||||
toast.showToast($t('exportingRecords'), 'info');
|
||||
const { startDate, endDate } = exportFilters.value;
|
||||
let workerIds = selectedWorkerIds.value.join(',');
|
||||
|
||||
@@ -516,7 +516,7 @@ const exportWorkHours = async () => {
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (_err) {
|
||||
toast.showToast('Failed to export records.', 'error');
|
||||
toast.showToast($t('exportRecordsFailed'), 'error');
|
||||
} finally {
|
||||
exportLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<transition name="app-toast">
|
||||
<div
|
||||
v-if="visible"
|
||||
class="fixed z-[9999] bottom-4 right-4 p-4 rounded-lg shadow-lg max-w-xs"
|
||||
class="fixed z-9999 bottom-4 right-4 p-4 rounded-lg shadow-lg max-w-xs"
|
||||
:class="{
|
||||
'bg-green-100 text-green-800 border border-green-200': type === 'success',
|
||||
'bg-red-100 text-red-800 border border-red-200': type === 'error',
|
||||
|
||||
@@ -60,6 +60,7 @@ export function useToast() {
|
||||
key: toast.id,
|
||||
message: toast.message,
|
||||
type: toast.type,
|
||||
class: "z-9999",
|
||||
duration: toast.duration,
|
||||
persistent: !!toast.actions,
|
||||
onClose: () => removeToast(toast.id)
|
||||
|
||||
+27
-1
@@ -265,10 +265,36 @@
|
||||
"managerSettings": "Manager Settings",
|
||||
"managerStatus": "Manager Status",
|
||||
"confirmDeleteWorker": "Are you sure you want to delete this worker? This will soft-delete their account.",
|
||||
"confirmClearDevice": "Are you sure you want to clear this device? The worker will need to re-login.",
|
||||
"view_all": "View All",
|
||||
"edit_workers": "Edit Workers",
|
||||
"manage_resources": "Manage Resources",
|
||||
"manager_permissions": "Manager Permissions",
|
||||
"confirmDelete": "Are you sure you want to delete this?",
|
||||
"confirm": "Confirm"
|
||||
"confirm": "Confirm",
|
||||
"scheduleUpdateFailed": "Failed to update schedule. Please try again.",
|
||||
"confirmApplyChanges": "Are you sure you want to apply these schedule changes?",
|
||||
"scheduleUpdateSuccess": "Schedule updated successfully",
|
||||
"fetchRecordsFailed": "Failed to fetch records",
|
||||
"loadDetailsFailed": "Failed to load details",
|
||||
"fetchQRCodesFailed": "Failed to fetch QR codes",
|
||||
"generateQRCodeFailed": "Failed to generate QR code image",
|
||||
"createQRCodeFailed": "Failed to create QR code",
|
||||
"updateQRStatusFailed": "Failed to update QR code status",
|
||||
"qrCodeDeleted": "QR code deleted successfully",
|
||||
"deleteQRCodeFailed": "Failed to delete QR code",
|
||||
"workerAdded": "Worker added successfully",
|
||||
"addUserError": "Error adding user",
|
||||
"workerSoftDeleted": "Worker soft-deleted successfully",
|
||||
"deviceCleared": "Worker device cleared successfully",
|
||||
"clearDeviceFailed": "Failed to clear device",
|
||||
"savingSettings": "Saving settings...",
|
||||
"exportingRecords": "Exporting records...",
|
||||
"exportRecordsFailed": "Failed to export records",
|
||||
"fetchManagersFailed": "Failed to fetch managers",
|
||||
"managerSettingsSaved": "Manager settings saved successfully",
|
||||
"saveSettingsFailed": "Failed to save settings",
|
||||
"managerAdded": "Manager added successfully",
|
||||
"managerDeleted": "Manager deleted successfully",
|
||||
"deleteManagerFailed": "Failed to delete manager"
|
||||
}
|
||||
|
||||
+66
-5
@@ -237,13 +237,13 @@
|
||||
"error.criticalServer": "Ralat kritikal pada pelayan telah berlaku. Sila hubungi sokongan.",
|
||||
|
||||
"dangerZone": "Zon Bahaya",
|
||||
"clearDeviceDescription": "Ini akan memadam semua data pekerja dari peranti. Gunakan dengan berhati-hati.",
|
||||
"clearDeviceDescription": "Nyahpaut Akaun dengan Peranti.",
|
||||
"settings": "Tetapan",
|
||||
"employeeSettings": "Tetapan Pekerja",
|
||||
"accountSettings": "Tetapan Akaun",
|
||||
"workerStatus": "Status Pekerja",
|
||||
"activeAccount": "Akaun Aktif",
|
||||
"deleteDescription": "Tindakan ini tidak boleh diundur. Semua data akan dipadam secara kekal.",
|
||||
"workerStatus": "Status Akaun",
|
||||
"activeAccount": "Benarkan Log Masuk",
|
||||
"deleteDescription": "Pengguna akan dipadam.",
|
||||
"saveChanges": "Simpan Perubahan",
|
||||
"confirmDeleteWorker": "Adakah anda pasti mahu memadam pekerja ini? Ini akan memadam akaun mereka secara lembut.",
|
||||
"managerPermissions": "Pengurus",
|
||||
@@ -252,8 +252,69 @@
|
||||
"loadingManagers": "Memuatkan pengurus...",
|
||||
"managerSettings": "Tetapan Pengurus",
|
||||
"managerStatus": "Status Pengurus",
|
||||
"confirmClearDevice": "Adakah anda pasti mahu mengosongkan peranti ini? Pekerja perlu log masuk semula.",
|
||||
"view_all": "Lihat Semua",
|
||||
"edit_workers": "Sunting Pekerja",
|
||||
"manage_resources": "Urus Sumber",
|
||||
"manager_permissions": "Kebenaran Pengurus"
|
||||
"manager_permissions": "Kebenaran Pengurus",
|
||||
"confirmDelete": "Adakah anda pasti mahu memadam ini?",
|
||||
"confirm": "Sahkan",
|
||||
|
||||
"can_view_workers": "Lihat Pekerja",
|
||||
"can_edit_workers": "Urus Pekerja",
|
||||
"can_view_alerts": "Lihat Amaran",
|
||||
"can_view_geofences": "Lihat Geofences",
|
||||
"can_manage_geofences": "Urus Geofences",
|
||||
"can_view_qrcodes": "Lihat Kod QR",
|
||||
"can_manage_qrcodes": "Urus Kod QR",
|
||||
"can_view_reports": "Lihat Laporan",
|
||||
"can_manage_killswitch": "Urus Jadual",
|
||||
"can_manage_permissions": "Urus Kebenaran",
|
||||
"can_edit_managers": "Sunting Pengurus",
|
||||
"can_delete_managers": "Padam Pengurus",
|
||||
|
||||
"Worker added successfully": "Pekerja berjaya ditambah",
|
||||
"Worker soft-deleted successfully.": "Pekerja berjaya dipadam (soft delete)",
|
||||
"Worker device cleared successfully.": "Peranti pekerja berjaya dikosongkan",
|
||||
"Manager settings saved successfully!": "Tetapan pengurus berjaya disimpan!",
|
||||
"Manager added successfully!": "Pengurus berjaya ditambah!",
|
||||
"Manager Deleted successfully.": "Pengurus berjaya dipadam.",
|
||||
"QR code deleted successfully": "Kod QR berjaya dipadam",
|
||||
"Failed to fetch records.": "Gagal mengambil rekod.",
|
||||
"Failed to load details.": "Gagal memuatkan butiran.",
|
||||
"Failed to fetch QR codes": "Gagal mengambil kod QR",
|
||||
"Failed to generate QR code image": "Gagal menjana imej kod QR",
|
||||
"Failed to create QR code": "Gagal mencipta kod QR",
|
||||
"Failed to update QR code status": "Gagal mengemas kini status kod QR",
|
||||
"Failed to delete QR code": "Gagal memadam kod QR",
|
||||
"Failed to export records.": "Gagal mengeksport rekod.",
|
||||
"Failed to fetch managers.": "Gagal mengambil senarai pengurus.",
|
||||
"Failed to delete manager.": "Gagal memadam pengurus.",
|
||||
"Saving settings...": "Menyimpan tetapan...",
|
||||
"Exporting records...": "Mengeksport rekod...",
|
||||
"scheduleUpdateFailed": "Gagal mengemas kini jadual. Sila cuba lagi.",
|
||||
"confirmApplyChanges": "Adakah anda pasti mahu mengguna pakai perubahan jadual ini?",
|
||||
"scheduleUpdateSuccess": "Jadual berjaya dikemas kini",
|
||||
"fetchRecordsFailed": "Gagal mengambil rekod",
|
||||
"loadDetailsFailed": "Gagal memuatkan butiran",
|
||||
"fetchQRCodesFailed": "Gagal mengambil kod QR",
|
||||
"generateQRCodeFailed": "Gagal menjana imej kod QR",
|
||||
"createQRCodeFailed": "Gagal mencipta kod QR",
|
||||
"updateQRStatusFailed": "Gagal mengemas kini status kod QR",
|
||||
"qrCodeDeleted": "Kod QR berjaya dipadam",
|
||||
"deleteQRCodeFailed": "Gagal memadam kod QR",
|
||||
"workerAdded": "Pekerja berjaya ditambah",
|
||||
"addUserError": "Ralat menambah pengguna",
|
||||
"workerSoftDeleted": "Pekerja berjaya dipadam (soft delete)",
|
||||
"deviceCleared": "Peranti pekerja berjaya dikosongkan",
|
||||
"clearDeviceFailed": "Gagal mengosongkan peranti",
|
||||
"savingSettings": "Menyimpan tetapan...",
|
||||
"exportingRecords": "Mengeksport rekod...",
|
||||
"exportRecordsFailed": "Gagal mengeksport rekod",
|
||||
"fetchManagersFailed": "Gagal mengambil senarai pengurus",
|
||||
"managerSettingsSaved": "Tetapan pengurus berjaya disimpan",
|
||||
"saveSettingsFailed": "Gagal menyimpan tetapan",
|
||||
"managerAdded": "Pengurus berjaya ditambah",
|
||||
"managerDeleted": "Pengurus berjaya dipadam",
|
||||
"deleteManagerFailed": "Gagal memadam pengurus"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user