Add attendance hub, categorized sidebar, and UI preferences page.
Improve dashboard and navigation access to attendance, group the right-side menu by category, and let each account save default theme and font size.
This commit is contained in:
@@ -4,6 +4,7 @@ import { ref, computed } from 'vue';
|
||||
import { authApi } from '@/api/authApi';
|
||||
import Cookies from 'js-cookie';
|
||||
import { usePermissionStore } from './permissionStore';
|
||||
import { useThemeStore } from './themeStore';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const accessToken = ref(localStorage.getItem('accessToken') || Cookies.get('accessToken') || '');
|
||||
@@ -53,6 +54,9 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
const roleName = data.user?.role?.name || data.role || '';
|
||||
permissionStore.setPermissions(perms, roleName);
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
themeStore.loadFromUser(data.user);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { authApi } from '@/api/authApi';
|
||||
import { useAuthStore } from './authStore';
|
||||
import { useThemeStore } from './themeStore';
|
||||
|
||||
export const usePermissionStore = defineStore('permission', () => {
|
||||
const permissions = ref(JSON.parse(localStorage.getItem('permissions') || '[]'));
|
||||
@@ -31,6 +33,13 @@ export const usePermissionStore = defineStore('permission', () => {
|
||||
const perms = userData.role?.permissions || [];
|
||||
const role = userData.role?.name || '';
|
||||
setPermissions(perms, role);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
authStore.setUser(userData);
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
themeStore.loadFromUser(userData);
|
||||
|
||||
return permissions.value;
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch user permissions:', err);
|
||||
|
||||
+127
-9
@@ -1,35 +1,153 @@
|
||||
// /src/stores/themeStore.js
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { authApi } from '@/api/authApi';
|
||||
|
||||
const FONT_SIZE_VALUES = {
|
||||
small: '12px',
|
||||
medium: '14px',
|
||||
large: '16px'
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'uiPreferences';
|
||||
|
||||
function readStoredPreferences() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeStoredPreferences(preferences) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
|
||||
}
|
||||
|
||||
export const useThemeStore = defineStore('theme', () => {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const isDarkMode = ref(savedTheme ? savedTheme === 'dark' : prefersDark);
|
||||
const stored = readStoredPreferences();
|
||||
const themePreference = ref(stored?.theme || 'system');
|
||||
const fontSize = ref(stored?.fontSize || 'medium');
|
||||
const isDarkMode = ref(false);
|
||||
let systemThemeListener = null;
|
||||
|
||||
function resolveDarkMode() {
|
||||
if (themePreference.value === 'dark') return true;
|
||||
if (themePreference.value === 'light') return false;
|
||||
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
isDarkMode.value = resolveDarkMode();
|
||||
const htmlEl = document.documentElement;
|
||||
if (isDarkMode.value) {
|
||||
htmlEl.classList.add('dark-mode', 'dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
htmlEl.classList.remove('dark-mode', 'dark');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
isDarkMode.value = !isDarkMode.value;
|
||||
function applyFontSize() {
|
||||
const size = FONT_SIZE_VALUES[fontSize.value] || FONT_SIZE_VALUES.medium;
|
||||
document.documentElement.style.setProperty('--app-font-size', size);
|
||||
document.documentElement.dataset.fontSize = fontSize.value;
|
||||
}
|
||||
|
||||
function persistLocal() {
|
||||
writeStoredPreferences({
|
||||
theme: themePreference.value,
|
||||
fontSize: fontSize.value
|
||||
});
|
||||
}
|
||||
|
||||
function applyPreferences() {
|
||||
applyTheme();
|
||||
applyFontSize();
|
||||
persistLocal();
|
||||
}
|
||||
|
||||
function setupSystemThemeListener() {
|
||||
if (!window.matchMedia) return;
|
||||
if (systemThemeListener) {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', systemThemeListener);
|
||||
}
|
||||
systemThemeListener = () => {
|
||||
if (themePreference.value === 'system') {
|
||||
applyTheme();
|
||||
}
|
||||
};
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', systemThemeListener);
|
||||
}
|
||||
|
||||
function loadFromUser(user) {
|
||||
if (!user?.preferences) return;
|
||||
if (user.preferences.theme) {
|
||||
themePreference.value = user.preferences.theme;
|
||||
}
|
||||
if (user.preferences.fontSize) {
|
||||
fontSize.value = user.preferences.fontSize;
|
||||
}
|
||||
applyPreferences();
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
themePreference.value = isDarkMode.value ? 'light' : 'dark';
|
||||
applyPreferences();
|
||||
saveToServer();
|
||||
}
|
||||
|
||||
function setThemePreference(value) {
|
||||
themePreference.value = value;
|
||||
applyPreferences();
|
||||
}
|
||||
|
||||
function setFontSize(value) {
|
||||
fontSize.value = value;
|
||||
applyFontSize();
|
||||
persistLocal();
|
||||
}
|
||||
|
||||
async function saveToServer() {
|
||||
try {
|
||||
const res = await authApi.updateSelf({
|
||||
preferences: {
|
||||
theme: themePreference.value,
|
||||
fontSize: fontSize.value
|
||||
}
|
||||
});
|
||||
const user = res.data || res;
|
||||
return user;
|
||||
} catch (err) {
|
||||
console.warn('Failed to save UI preferences:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function initTheme() {
|
||||
applyTheme();
|
||||
applyPreferences();
|
||||
setupSystemThemeListener();
|
||||
}
|
||||
|
||||
return {
|
||||
isDarkMode,
|
||||
themePreference,
|
||||
fontSize,
|
||||
fontSizeOptions: [
|
||||
{ label: 'کوچک', value: 'small', sample: '۱۲px' },
|
||||
{ label: 'متوسط', value: 'medium', sample: '۱۴px' },
|
||||
{ label: 'بزرگ', value: 'large', sample: '۱۶px' }
|
||||
],
|
||||
themeOptions: [
|
||||
{ label: 'روشن', value: 'light', icon: 'pi pi-sun' },
|
||||
{ label: 'تیره', value: 'dark', icon: 'pi pi-moon' },
|
||||
{ label: 'سیستم', value: 'system', icon: 'pi pi-desktop' }
|
||||
],
|
||||
toggleTheme,
|
||||
initTheme
|
||||
setThemePreference,
|
||||
setFontSize,
|
||||
loadFromUser,
|
||||
saveToServer,
|
||||
initTheme,
|
||||
applyPreferences
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user