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
|
// 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
|
let connection; // Define connection here to ensure it's accessible in the 'finally' block
|
||||||
try {
|
try {
|
||||||
const { datesToEnable, datesToDisable } = req.body;
|
const { datesToEnable, datesToDisable } = req.body;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="min-h-screen bg-gray-100 text-gray-900 dark:bg-gray-900 dark:text-gray-100 transition-colors duration-300">
|
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" />
|
<component v-for="toast in renderToasts()" :is="toast" :key="toast.key" />
|
||||||
</div>
|
</div>
|
||||||
<header
|
<header
|
||||||
|
|||||||
@@ -198,9 +198,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed, watch } from 'vue';
|
import { ref, onMounted, computed, watch, nextTick } from 'vue';
|
||||||
import { apiFetch } from '@/api.js';
|
import { apiFetch } from '@/api.js';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const managers = ref([]);
|
const managers = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@@ -229,6 +230,7 @@ const newManager = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
const { t: $t } = useI18n();
|
||||||
|
|
||||||
const isManagerFormValid = computed(() =>
|
const isManagerFormValid = computed(() =>
|
||||||
newManager.value.fullName &&
|
newManager.value.fullName &&
|
||||||
@@ -243,6 +245,8 @@ const permissionsList = ref([
|
|||||||
{ key: 'manager_permissions', label: 'Manage Managers (Add/Edit/Delete & Permissions)' },
|
{ key: 'manager_permissions', label: 'Manage Managers (Add/Edit/Delete & Permissions)' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const initialLoad = ref(true);
|
||||||
|
|
||||||
const totalPages = computed(() => {
|
const totalPages = computed(() => {
|
||||||
const pages = Math.ceil(totalManagers.value / pageSize.value);
|
const pages = Math.ceil(totalManagers.value / pageSize.value);
|
||||||
return pages < 1 ? 1 : pages;
|
return pages < 1 ? 1 : pages;
|
||||||
@@ -274,7 +278,7 @@ const fetchManagers = async (page = currentPage.value) => {
|
|||||||
errorMessage.value = 'Failed to fetch managers.';
|
errorMessage.value = 'Failed to fetch managers.';
|
||||||
managers.value = [];
|
managers.value = [];
|
||||||
totalManagers.value = 0;
|
totalManagers.value = 0;
|
||||||
toast.showToast('Failed to fetch managers.', 'error');
|
toast.showToast($t('fetchManagersFailed'), 'error');
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@@ -296,8 +300,12 @@ const jumpToPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openSettingsModal = (manager) => {
|
const openSettingsModal = (manager) => {
|
||||||
|
initialLoad.value = true;
|
||||||
editingManager.value = { ...manager };
|
editingManager.value = { ...manager };
|
||||||
isSettingsModalVisible.value = true;
|
isSettingsModalVisible.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
initialLoad.value = false;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeSettingsModal = () => {
|
const closeSettingsModal = () => {
|
||||||
@@ -315,49 +323,69 @@ const saveManagerSettings = async () => {
|
|||||||
saving.value = true;
|
saving.value = true;
|
||||||
passwordErrorMessage.value = '';
|
passwordErrorMessage.value = '';
|
||||||
passwordSuccessMessage.value = '';
|
passwordSuccessMessage.value = '';
|
||||||
|
console.log('Starting saveManagerSettings...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save permissions - convert to new structure
|
// Save all changes in parallel for better performance
|
||||||
const permissionsToSave = {
|
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,
|
view_all: editingManager.value.view_all || false,
|
||||||
edit_workers: editingManager.value.edit_workers || false,
|
edit_workers: editingManager.value.edit_workers || false,
|
||||||
manage_resources: editingManager.value.manage_resources || false,
|
manage_resources: editingManager.value.manage_resources || false,
|
||||||
manager_permissions: editingManager.value.manager_permissions || 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
|
// Save manager details
|
||||||
await apiFetch(`/api/managers/managers/${editingManager.value.id}`, {
|
apiFetch(`/api/managers/managers/${editingManager.value.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
department: editingManager.value.department,
|
department: editingManager.value.department,
|
||||||
position: editingManager.value.position,
|
position: editingManager.value.position,
|
||||||
status: editingManager.value.isActive ? 'active' : 'inactive',
|
status: editingManager.value.isActive ? 'active' : 'inactive',
|
||||||
}),
|
}),
|
||||||
});
|
}),
|
||||||
|
|
||||||
// Save password if changed
|
// Save password if changed
|
||||||
if (newPassword.value) {
|
newPassword.value && newPassword.value === confirmNewPassword.value
|
||||||
if (newPassword.value !== confirmNewPassword.value) {
|
? apiFetch(`/api/managers/managers/${editingManager.value.id}/password`, {
|
||||||
throw new Error('Passwords do not match.');
|
|
||||||
}
|
|
||||||
await apiFetch(`/api/managers/managers/${editingManager.value.id}/password`, {
|
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify({ newPassword: newPassword.value }),
|
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);
|
await fetchManagers(currentPage.value);
|
||||||
closeSettingsModal();
|
toast.showToast($t('managerSettingsSaved'), 'success');
|
||||||
toast.showToast('Manager settings saved successfully!', '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) {
|
} catch (err) {
|
||||||
|
console.error('Error in saveManagerSettings:', err);
|
||||||
passwordErrorMessage.value = err.message || 'Failed to save settings.';
|
passwordErrorMessage.value = err.message || 'Failed to save settings.';
|
||||||
toast.showToast(err.message || 'Failed to save settings.', 'error');
|
toast.showToast(err.message || 'Failed to save settings.', 'error');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -413,10 +441,9 @@ const closeAddManagerModal = () => {
|
|||||||
|
|
||||||
await fetchManagers(1); // Auto-refetch the manager list after adding
|
await fetchManagers(1); // Auto-refetch the manager list after adding
|
||||||
closeAddManagerModal(); // Auto-close the modal after successful addition
|
closeAddManagerModal(); // Auto-close the modal after successful addition
|
||||||
toast.showToast('Manager added successfully!', 'success');
|
toast.showToast($t('managerAddedSuccess'), 'success');
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
const displayMessage = err.message || err.sqlMessage || 'Error adding manager';
|
toast.showToast($t('addManagerError'), 'error');
|
||||||
toast.showToast(displayMessage, 'error');
|
|
||||||
} finally {
|
} finally {
|
||||||
addingManager.value = false;
|
addingManager.value = false;
|
||||||
}
|
}
|
||||||
@@ -425,12 +452,12 @@ const closeAddManagerModal = () => {
|
|||||||
const deleteManager = async (id) => {
|
const deleteManager = async (id) => {
|
||||||
try {
|
try {
|
||||||
await apiFetch(`/api/managers/managers/${id}`, { method: 'DELETE' });
|
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
|
// 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);
|
fetchManagers(managers.value.length === 1 && currentPage.value > 1 ? currentPage.value - 1 : currentPage.value);
|
||||||
closeSettingsModal();
|
closeSettingsModal();
|
||||||
} catch (_err) {
|
} 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
|
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);
|
await fetchWorkers(1);
|
||||||
newWorker.value = { fullName: '', username: '', password: '', department: '', position: '' };
|
newWorker.value = { fullName: '', username: '', password: '', department: '', position: '' };
|
||||||
toast.showToast('Worker added successfully', 'success');
|
toast.showToast($t('workerAdded'), 'success');
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
toast.showToast(_err.message || 'Error adding user.', 'error');
|
toast.showToast(_err.message || $t('addUserError'), 'error');
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@@ -354,7 +354,7 @@ const deleteWorker = async (id) => {
|
|||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
try {
|
try {
|
||||||
await apiFetch(`/api/managers/workers/${id}`, { method: 'DELETE' });
|
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);
|
fetchWorkers(workers.value.length === 1 && currentPage.value > 1 ? currentPage.value - 1 : currentPage.value);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
errorMessage.value = 'Failed to soft-delete worker.';
|
errorMessage.value = 'Failed to soft-delete worker.';
|
||||||
@@ -365,9 +365,9 @@ const clearDevice = async (workerId) => {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
try {
|
try {
|
||||||
await apiFetch(`/api/managers/workers/${workerId}/reset-device`, { method: 'PUT' });
|
await apiFetch(`/api/managers/workers/${workerId}/reset-device`, { method: 'PUT' });
|
||||||
toast.showToast('Worker device cleared successfully.', 'success');
|
toast.showToast($t('deviceCleared'), 'success');
|
||||||
} catch (_err) {
|
} 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 = '';
|
passwordSuccessMessage.value = '';
|
||||||
let passwordUpdated = false;
|
let passwordUpdated = false;
|
||||||
let detailsUpdated = false;
|
let detailsUpdated = false;
|
||||||
toast.showToast('Saving settings...', 'info');
|
toast.showToast($t('savingSettings'), 'info');
|
||||||
|
|
||||||
// Handle password change
|
// Handle password change
|
||||||
if (newPassword.value || confirmNewPassword.value) {
|
if (newPassword.value || confirmNewPassword.value) {
|
||||||
@@ -495,7 +495,7 @@ const toggleSelectAll = (event) => {
|
|||||||
const exportWorkHours = async () => {
|
const exportWorkHours = async () => {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
exportLoading.value = true;
|
exportLoading.value = true;
|
||||||
toast.showToast('Exporting records...', 'info');
|
toast.showToast($t('exportingRecords'), 'info');
|
||||||
const { startDate, endDate } = exportFilters.value;
|
const { startDate, endDate } = exportFilters.value;
|
||||||
let workerIds = selectedWorkerIds.value.join(',');
|
let workerIds = selectedWorkerIds.value.join(',');
|
||||||
|
|
||||||
@@ -516,7 +516,7 @@ const exportWorkHours = async () => {
|
|||||||
a.remove();
|
a.remove();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
toast.showToast('Failed to export records.', 'error');
|
toast.showToast($t('exportRecordsFailed'), 'error');
|
||||||
} finally {
|
} finally {
|
||||||
exportLoading.value = false;
|
exportLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<transition name="app-toast">
|
<transition name="app-toast">
|
||||||
<div
|
<div
|
||||||
v-if="visible"
|
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="{
|
:class="{
|
||||||
'bg-green-100 text-green-800 border border-green-200': type === 'success',
|
'bg-green-100 text-green-800 border border-green-200': type === 'success',
|
||||||
'bg-red-100 text-red-800 border border-red-200': type === 'error',
|
'bg-red-100 text-red-800 border border-red-200': type === 'error',
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export function useToast() {
|
|||||||
key: toast.id,
|
key: toast.id,
|
||||||
message: toast.message,
|
message: toast.message,
|
||||||
type: toast.type,
|
type: toast.type,
|
||||||
|
class: "z-9999",
|
||||||
duration: toast.duration,
|
duration: toast.duration,
|
||||||
persistent: !!toast.actions,
|
persistent: !!toast.actions,
|
||||||
onClose: () => removeToast(toast.id)
|
onClose: () => removeToast(toast.id)
|
||||||
|
|||||||
+27
-1
@@ -265,10 +265,36 @@
|
|||||||
"managerSettings": "Manager Settings",
|
"managerSettings": "Manager Settings",
|
||||||
"managerStatus": "Manager Status",
|
"managerStatus": "Manager Status",
|
||||||
"confirmDeleteWorker": "Are you sure you want to delete this worker? This will soft-delete their account.",
|
"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",
|
"view_all": "View All",
|
||||||
"edit_workers": "Edit Workers",
|
"edit_workers": "Edit Workers",
|
||||||
"manage_resources": "Manage Resources",
|
"manage_resources": "Manage Resources",
|
||||||
"manager_permissions": "Manager Permissions",
|
"manager_permissions": "Manager Permissions",
|
||||||
"confirmDelete": "Are you sure you want to delete this?",
|
"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.",
|
"error.criticalServer": "Ralat kritikal pada pelayan telah berlaku. Sila hubungi sokongan.",
|
||||||
|
|
||||||
"dangerZone": "Zon Bahaya",
|
"dangerZone": "Zon Bahaya",
|
||||||
"clearDeviceDescription": "Ini akan memadam semua data pekerja dari peranti. Gunakan dengan berhati-hati.",
|
"clearDeviceDescription": "Nyahpaut Akaun dengan Peranti.",
|
||||||
"settings": "Tetapan",
|
"settings": "Tetapan",
|
||||||
"employeeSettings": "Tetapan Pekerja",
|
"employeeSettings": "Tetapan Pekerja",
|
||||||
"accountSettings": "Tetapan Akaun",
|
"accountSettings": "Tetapan Akaun",
|
||||||
"workerStatus": "Status Pekerja",
|
"workerStatus": "Status Akaun",
|
||||||
"activeAccount": "Akaun Aktif",
|
"activeAccount": "Benarkan Log Masuk",
|
||||||
"deleteDescription": "Tindakan ini tidak boleh diundur. Semua data akan dipadam secara kekal.",
|
"deleteDescription": "Pengguna akan dipadam.",
|
||||||
"saveChanges": "Simpan Perubahan",
|
"saveChanges": "Simpan Perubahan",
|
||||||
"confirmDeleteWorker": "Adakah anda pasti mahu memadam pekerja ini? Ini akan memadam akaun mereka secara lembut.",
|
"confirmDeleteWorker": "Adakah anda pasti mahu memadam pekerja ini? Ini akan memadam akaun mereka secara lembut.",
|
||||||
"managerPermissions": "Pengurus",
|
"managerPermissions": "Pengurus",
|
||||||
@@ -252,8 +252,69 @@
|
|||||||
"loadingManagers": "Memuatkan pengurus...",
|
"loadingManagers": "Memuatkan pengurus...",
|
||||||
"managerSettings": "Tetapan Pengurus",
|
"managerSettings": "Tetapan Pengurus",
|
||||||
"managerStatus": "Status Pengurus",
|
"managerStatus": "Status Pengurus",
|
||||||
|
"confirmClearDevice": "Adakah anda pasti mahu mengosongkan peranti ini? Pekerja perlu log masuk semula.",
|
||||||
"view_all": "Lihat Semua",
|
"view_all": "Lihat Semua",
|
||||||
"edit_workers": "Sunting Pekerja",
|
"edit_workers": "Sunting Pekerja",
|
||||||
"manage_resources": "Urus Sumber",
|
"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