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
5 changes: 5 additions & 0 deletions src/components/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ const menuSections = [
icon: "mdi-file-excel-outline",
},
{ label: "Cảnh báo tồn kho", to: "/alerts", icon: "mdi-alert-outline" },
{
label: "Hồ sơ",
to: "/profile",
icon: "mdi-account-outline",
},
{
label: "Nhân viên",
to: "/employees",
Expand Down
2 changes: 2 additions & 0 deletions src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import PendingExportApprovalsView from '../views/PendingExportApprovalsView.vue'
import ImportExcelView from '../views/ImportExcelView.vue'
import AlertsView from '../views/AlertsView.vue'
import UsersView from '../views/UsersView.vue'
import ProfileView from '../views/ProfileView.vue'
import EmployeeListView from '../views/employees/EmployeeListView.vue'
import CategoryListView from '../views/categories/CategoryListView.vue'

Expand All @@ -42,6 +43,7 @@ const routes = [
{ path: '/approvals', component: ApprovalsView, meta: { title: 'Chờ duyệt' } },
{ path: '/import-excel', component: ImportExcelView, meta: { title: 'Import Excel' } },
{ path: '/alerts', component: AlertsView, meta: { title: 'Cảnh báo tồn kho' } },
{ path: '/profile', component: ProfileView, meta: { title: 'Hồ sơ' } },
{ path: '/employees', component: EmployeeListView, meta: { title: 'Nhân viên' } },
{ path: '/users', component: UsersView, meta: { title: 'Nhân viên & phân quyền' } },

Expand Down
33 changes: 33 additions & 0 deletions src/services/dashboardService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from "axios"
import { clearAuth, getAuthorizationHeader } from "./authService"

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://localhost:8080"
const API_TIMEOUT_MS = 15000

const dashboardClient = axios.create({
baseURL: API_BASE_URL,
timeout: API_TIMEOUT_MS,
headers: { "Content-Type": "application/json" },
})

const request = async (config, fallback) => {
try {
const { data } = await dashboardClient.request({
...config,
headers: getAuthorizationHeader(),
})
return data
} catch (error) {
if (error.response?.status === 401) clearAuth()
throw {
status: error.response?.status || 0,
message: error.response?.data?.message || fallback,
errors: error.response?.data?.errors || {},
}
}
}

export const getDashboardOverview = () => request(
{ method: "get", url: "/api/dashboard/overview" },
"Không thể tải dữ liệu dashboard.",
)
5 changes: 5 additions & 0 deletions src/services/productService.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axios from 'axios'
import { clearAuth, getAuthorizationHeader } from './authService'
import { canAccessRoute } from '../services/permissionService'

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'

Expand All @@ -11,6 +12,10 @@ const productClient = axios.create({
})

export async function getProducts({ page = 0, size = 10, keyword = '', categoryId = '', status = '' } = {}) {
if (!canAccessRoute('/products')) {
return { content: [], totalElements: 0 }
}

try {
const { data } = await productClient.get('/api/products', {
headers: getAuthorizationHeader(),
Expand Down
114 changes: 114 additions & 0 deletions src/services/profileService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import axios from 'axios'
import { clearAuth, getAuthorizationHeader, getCurrentUser } from './authService'

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'
const API_TIMEOUT_MS = 15000

const profileClient = axios.create({
baseURL: API_BASE_URL,
timeout: API_TIMEOUT_MS,
headers: {
'Content-Type': 'application/json',
},
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export async function getCurrentProfile() {
const fallbackProfile = normalizeProfile(getCurrentUser())

let lastError = null

try {
const endpoints = ['/api/auth/me', '/api/auth/profile']
const currentUser = getCurrentUser()

if (currentUser?.employeeId) {
endpoints.unshift(`/api/employees/${currentUser.employeeId}`)
}

for (const endpoint of endpoints) {
try {
const { data } = await profileClient.get(endpoint, {
headers: getAuthorizationHeader(),
})

const normalized = normalizeProfile(data, fallbackProfile)
if (hasProfileData(normalized)) {
return normalized
}
} catch (error) {
const normalizedError = normalizeProfileError(error, 'Phiên đăng nhập đã hết hạn.')

if (normalizedError.status === 401) {
clearAuth()
throw normalizedError
}

lastError = normalizedError

if (error.response?.status === 404) {
continue
Comment on lines +46 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Không lưu lỗi 404 vào lastError.

Line 46 lưu 404 trước khi Line 48 chuyển sang endpoint tiếp theo. Nếu tất cả endpoint đều trả 404 và không có fallbackProfile, hàm sẽ ném lỗi 404 tại Line 62 thay vì trả null. UI hồ sơ sẽ hiển thị error state thay vì empty state.

Chỉ gán lastError sau nhánh 404.

Đề xuất sửa
-        lastError = normalizedError
-
         if (error.response?.status === 404) {
           continue
         }
 
+        lastError = normalizedError
+
         if (error.response?.status && error.response.status < 500) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/profileService.js` around lines 46 - 49, Trong luồng thử các
endpoint của hàm profile service, xử lý trạng thái 404 trước khi gán giá trị cho
lastError. Khi error.response?.status là 404, tiếp tục endpoint kế tiếp mà không
lưu lỗi; chỉ cập nhật lastError cho các lỗi khác để khi mọi endpoint đều không
có hồ sơ, hàm trả về null theo luồng hiện có.

Source: Path instructions

}

if (error.response?.status && error.response.status < 500) {
break
}
}
}

if (hasProfileData(fallbackProfile)) {
return fallbackProfile
}

if (lastError) {
throw lastError
}

return null
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error) {
const normalizedError = normalizeProfileError(error, 'Không thể tải thông tin hồ sơ.')

if (normalizedError.status === 401) {
throw normalizedError
}

if (hasProfileData(fallbackProfile)) {
return fallbackProfile
}

throw normalizedError
}
}

function normalizeProfile(data = {}, fallback = {}) {
return {
id: data?.id ?? data?.employeeId ?? fallback?.id ?? fallback?.employeeId ?? null,
employeeId: data?.employeeId ?? data?.id ?? fallback?.employeeId ?? fallback?.id ?? null,
fullName: data?.fullName ?? fallback?.fullName ?? '',
email: data?.email ?? fallback?.email ?? '',
role: data?.roleName ?? data?.role ?? data?.roleCode ?? fallback?.roleName ?? fallback?.role ?? fallback?.roleCode ?? '',
roleCode: data?.roleCode ?? fallback?.roleCode ?? '',
status: data?.status ?? fallback?.status ?? '',
}
}

function hasProfileData(profile) {
return Boolean(profile?.fullName || profile?.email || profile?.role || profile?.status || profile?.employeeId)
}

function normalizeProfileError(error, fallbackMessage) {
if (error?.response?.status === 401) {
clearAuth()
}

if (error?.response?.data) {
return {
status: error.response.status,
message: error.response.data.message || fallbackMessage,
}
}

return {
status: error?.status || 0,
message: error?.message || fallbackMessage,
}
}
5 changes: 5 additions & 0 deletions src/services/warehouseService.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axios from 'axios'
import { clearAuth, getAuthorizationHeader } from './authService'
import { canAccessRoute } from '../services/permissionService'

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080'

Expand All @@ -17,6 +18,10 @@ const warehouseClient = axios.create({
* - Mã kho không được cho trùng và không cho phép sửa đổi để đảm bảo tính toàn vẹn dữ liệu.
*/
export async function getWarehouses({ keyword = '', status = '' } = {}) {
if (!canAccessRoute('/warehouses')) {
return { content: [], totalElements: 0 }
}

try {
const { data } = await warehouseClient.get('/api/warehouses', {
headers: getAuthorizationHeader(),
Expand Down
Loading