Skip to content

addStatisticsAndChart,fixbugAdminpage - #12

Merged
poner2547 merged 1 commit into
developfrom
feature/addminPageAndPageStatistics
Jul 7, 2026
Merged

poner2547 merged 1 commit into
developfrom
feature/addminPageAndPageStatistics

Conversation

@poner2547

@poner2547 poner2547 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

addStatistic addChart

Summary by CodeRabbit

  • New Features

    • Added a statistics dashboard with charts, ranked lists, and time-based views.
    • Added filtering for equipment by category and location, plus bulk selection and editing in admin tools.
    • Improved file import flows with confirmation steps and clearer error details.
  • Bug Fixes

    • Updated ticket and problem cards to show more accurate dates, status labels, and loading placeholders.
    • Enhanced ticket detail views with better image galleries and modal previews.
  • Style

    • Refreshed several tables, sidebars, cards, and loading states for a cleaner UI.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a statistics dashboard (page, sidebar, chart, hook, backend controllers/routes), introduces skeleton loading placeholders for cards/filters, expands equipment management with bulk editing and import confirmation flows, adds confirmation-dialog save/update flows to admin category/location/user pages, and reworks the ticket detail image gallery, plus various backend and styling adjustments.

Changes

Statistics Dashboard Feature

Layer / File(s) Summary
Route registration
client/src/App.jsx
Lazy-loads and mounts the new Statistics page at /statistics/*, removing the prior catch-all redirect.
Backend statistics endpoints
server/controllers/statisticController.js, server/routes/managementRoutes.js
Adds controllers for most categories, most upvoted tickets, and ticket stats aggregation, wired into new GET routes.
Statistics data hook
client/src/hooks/useStatistics.js
Fetches categories, upvoted tickets, and ticket stats, re-fetching stats on year/month change.
Statistics page, sidebar, chart UI
client/src/pages/Statistics.jsx, client/src/pages/Statistics.css, client/src/components/StatisticsSidebar.jsx, client/src/components/StatisticsSidebar.css, client/src/components/ChartForStatistic.jsx
Renders the statistics page with routed views, ranking cards, sidebar navigation, and an ApexCharts area chart.

Skeleton Loading UI

Layer / File(s) Summary
Card skeleton components
client/src/components/CardFinishProblem.jsx, client/src/components/CardPendingProblem.jsx/.css, client/src/components/componentsStyles/CardFinishProblem.css
Adds skeleton placeholders and formatted date rendering to finished/pending problem cards.
Filter skeleton component
client/src/components/FilterProblem.jsx
Adds a skeleton placeholder for the filter bar UI.
Dashboard skeleton wiring
client/src/pages/Dashboard.jsx, client/src/index.css
Conditionally renders skeleton placeholders during loading and adds a text-clamp utility class.

Equipment Management and Import

Layer / File(s) Summary
Data hooks and service
client/src/hooks/useEquipment.js, client/src/hooks/useEquiptmentCtg.js, client/src/services/equipmentService.js
Adds filtered equipment fetching, category hook, and an update API service.
Equipment controller endpoints
server/controllers/EquipmentControllers.js
Filters getEquipment by category/location, adjusts upload response contract, adds updateEquipment.
Import confirmation flow
client/src/components/componentsAdmin/ImportEquipments.jsx/.css, client/src/hooks/useImportEquipments.js
Adds upload-confirmation and error-list modals with updated hook state.
Bulk selection UI
client/src/pages/adminPage/AssetManagement.jsx/.css
Adds multi-select, bulk/single edit forms, filters, and update handlers.

Admin Confirmation Flows for Categories, Locations, and Users

Layer / File(s) Summary
Backend controller updates
server/controllers/managementControllers.js, server/routes/managementRoutes.js, client/src/hooks/useUsers.js
Adds location validation, standardized delete responses, and a role-update endpoint/hook.
Categories save confirmation
client/src/pages/adminPage/Categories.jsx/.css
Two-step confirm/save flow with loading/toast feedback.
Location status confirmation
client/src/pages/adminPage/LocationManagement.jsx
Confirmation dialogs and cascading status propagation for location/floor/room.
User role confirmation
client/src/pages/adminPage/UserManagement.jsx
Confirmation dialog for updating a user's role.

Ticket Detail Image Gallery and Status Handling

Layer / File(s) Summary
Image gallery and status UI
client/src/pages/DetailTicket.jsx/.css, client/src/utils/timeline.js
Modal-based image galleries, status labels, conditional rating, and timeline text updates.
Ticket update status handling
server/controllers/ticketControllers.js, server/routes/ticketRoutes.js
Adds ticketStatus updates and removes pending-status gate from the update route.

Miscellaneous Dependency, Styling, and Cleanup Changes

Layer / File(s) Summary
Dependencies and cleanup
client/package.json, client/src/pages/Tracking.css, client/src/pages/AddIssue.jsx, client/src/hooks/useTickets.js
Adds new packages, widens grid columns, removes duplicate import, exposes setIsLoading.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ImportEquipments
  participant ConfirmButton
  participant useImportEquipments
  participant Server

  User->>ImportEquipments: select file, click upload
  ImportEquipments->>ConfirmButton: open confirmation modal
  User->>ConfirmButton: confirm
  ConfirmButton->>useImportEquipments: uploadFile()
  useImportEquipments->>Server: POST file
  Server-->>useImportEquipments: response.data (errors or success)
  useImportEquipments-->>ImportEquipments: errorList or refetch()
  ImportEquipments-->>User: show error modal or cleared file state
Loading
sequenceDiagram
  participant Statistics
  participant useStatistics
  participant statisticController
  participant Prisma

  Statistics->>useStatistics: call with selectedYear, selectedMonth
  useStatistics->>statisticController: GET getMostCategoriesOfProblems
  useStatistics->>statisticController: GET getMostUpvotedTickets
  useStatistics->>statisticController: GET getTicket-stats
  statisticController->>Prisma: query ticket aggregates
  Prisma-->>statisticController: aggregated data
  statisticController-->>useStatistics: JSON response
  useStatistics-->>Statistics: mostCategoriesOfProblems, mostUpvotedTickets, ticketStats
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main changes: adding statistics/chart features and fixing admin page behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/addminPageAndPageStatistics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
client/src/App.jsx (1)

44-51: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Removed wildcard redirect leaves unmatched authenticated paths dangling.

Per the change details, the prior logged-in catch-all route (<Route path="*" element={<Navigate to="/" />} />) was removed while adding the new /statistics/* route. Now, any unmatched path for a logged-in user (typos, stale bookmarks, removed routes) will render nothing inside <Routes> instead of redirecting home, unlike the logged-out branch which still has <Route path="*" element={<Login />} />.

🩹 Suggested fix
                 <Route path="/statistics/*" element={<Statistics />} />
+                <Route path="*" element={<Navigate to="/" />} />
🤖 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 `@client/src/App.jsx` around lines 44 - 51, The authenticated route set in
App’s <Routes> is missing the previous catch-all redirect, so unmatched
logged-in paths now fall through with no render. Restore a wildcard route in the
authenticated branch using the existing routing pattern in App.jsx, and make it
redirect to the home/dashboard route the same way the logged-out branch handles
unknown paths. Keep the new /statistics/* route intact while ensuring all other
unmatched authenticated paths are covered.
client/src/pages/DetailTicket.jsx (1)

21-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add labels/styles for all ticket statuses
DetailTicket.jsx only maps pending, in_progress, and resolved, but the backend also uses canceled, rejected, and duplicate. Those statuses will render an empty badge here and have no matching .ticketStatus.* styles in DetailTicket.css. Add the missing entries or a fallback label.

🤖 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 `@client/src/pages/DetailTicket.jsx` around lines 21 - 41, DetailTicket.jsx
only handles a subset of ticket statuses, so canceled, rejected, and duplicate
can render with empty labels and no matching styling. Update the statusLabels
mapping in DetailTicket.jsx to include those missing statuses or add a safe
fallback label, and make sure DetailTicket.css includes matching .ticketStatus
classes for each status used by ticket.ticketStatus.
client/src/hooks/useUsers.js (1)

7-16: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

fetchUsers now rethrows but its mount-time call is unguarded.

fetchUsers previously only logged errors; it now rethrows (Line 14). The useEffect at Lines 27-29 calls fetchUsers() without .catch/await-try, so a failed initial fetch becomes an unhandled promise rejection.

🐛 Proposed fix
     useEffect(() => {
-        fetchUsers();
+        fetchUsers().catch(() => {});
     }, [fetchUsers]);

Also applies to: 27-29

🤖 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 `@client/src/hooks/useUsers.js` around lines 7 - 16, The initial mount-time
call to fetchUsers in useUsers is now unsafe because fetchUsers rethrows errors,
so the useEffect-triggered call can produce an unhandled promise rejection.
Update the useEffect that invokes fetchUsers to handle the returned promise
explicitly, either by awaiting it inside an async helper with try/catch or by
attaching a .catch handler, while keeping fetchUsers’ current error propagation
behavior intact.
🟡 Minor comments (6)
client/src/hooks/useStatistics.js-20-27 (1)

20-27: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

No fallback if response.data.data is missing.

setMostUpvotedTickets(response.data.data) has no guard; if the backend response shape changes or data is absent, this sets state to undefined, and Statistics.jsx immediately calls .map() on it, crashing the render.

🩹 Suggested fix
-            setMostUpvotedTickets(response.data.data);
+            setMostUpvotedTickets(response.data.data || []);
🤖 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 `@client/src/hooks/useStatistics.js` around lines 20 - 27, The
fetchMostUpvotedTickets logic in useStatistics should guard against missing
response.data.data before updating state, since setting it to undefined causes
Statistics.jsx to fail when it calls .map(). Update the axios response handling
in fetchMostUpvotedTickets so it always stores a safe array fallback when the
backend payload shape is absent or changed, and keep the error handling in the
same function consistent with that fallback.
server/controllers/statisticController.js-64-120 (1)

64-120: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Missing/invalid handling when month query param is absent.

If req.query.month is undefined (not 'all' and not a numeric string), execution falls into the else branch where parseInt(undefined) yields NaN, producing an Invalid Date range. The Prisma where filter then silently matches nothing, so the endpoint returns all-zero stats instead of surfacing an error or falling back to a sensible default (e.g., current month).

🐛 Proposed fix
         const { year, month } = req.query;
         const targetYear = year ? parseInt(year) : new Date().getFullYear();
+        const targetMonthRaw = month === undefined ? new Date().getMonth().toString() : month;

-        if (month === 'all') {
+        if (targetMonthRaw === 'all') {
🤖 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 `@server/controllers/statisticController.js` around lines 64 - 120, The
getTicketStats controller currently falls into the month-specific branch even
when req.query.month is missing, causing parseInt(month) to become NaN and the
Prisma date range to use Invalid Date. Update getTicketStats to validate the
month input before building the date window: if month is absent or not a valid
numeric month, either default to the current month or return a clear 400
response, and keep the existing month === 'all' path unchanged.
client/src/pages/adminPage/UserManagement.jsx-41-70 (1)

41-70: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Missing check that a user row is selected before confirming role update.

handleConfirmUpdateRole only validates formData.userRole. The role <select> (Lines 135-144) isn't disabled based on selectedId, so an admin can pick a role without clicking a table row first, sending updateRoleUser({ userId: null, ... }) and getting a generic backend error instead of a clear client-side message.

🛡️ Proposed fix
     const handleConfirmUpdateRole = () => {
-
+        if (!selectedId) {
+            setError('กรุณาเลือกผู้ใช้งานที่ต้องการแก้ไข');
+            return;
+        }
         if (!formData.userRole) {
             setError('กรุณาเลือกข้อมูลที่ต้องการจะอัพเดท')
             return;
         }
         setConfirmSubmit({ isOpen: true, })
     }
🤖 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 `@client/src/pages/adminPage/UserManagement.jsx` around lines 41 - 70, The role
update flow in UserManagement.jsx only checks formData.userRole, so
handleConfirmUpdateRole can proceed even when no table row is selected and
selectedId is null. Update handleConfirmUpdateRole to also validate that a user
row has been selected before opening confirmSubmit, and show a clear client-side
error if selectedId is missing. Use the existing selectedId,
handleConfirmUpdateRole, and handleUpdateRole flow to prevent calling
updateRoleUser with an invalid userId.
client/src/pages/adminPage/Categories.jsx-146-160 (1)

146-160: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Confirm dialog always says "add" even when updating a category.

title/message are hardcoded to "ยืนยันการเพิ่มประเภทปัญหา" (confirm adding), but handleSaveCategory also handles updates (when formData.ticketCtgId is set). Users updating an existing category see a misleading confirmation.

✏️ Proposed fix
                     <ConfirmButton
                         isOpen={confirmSubmit.isOpen}
-                        title="ยืนยันการเพิ่มประเภทปัญหา"
-                        message="คุณแน่ใจหรือไม่ว่าต้องการเพิ่มประเภทปัญหาใหม่นี้? โปรดตรวจสอบข้อมูลให้ถูกต้องก่อนยืนยัน"
+                        title={formData.ticketCtgId ? "ยืนยันการแก้ไขประเภทปัญหา" : "ยืนยันการเพิ่มประเภทปัญหา"}
+                        message={formData.ticketCtgId ? "คุณแน่ใจหรือไม่ว่าต้องการแก้ไขประเภทปัญหานี้?" : "คุณแน่ใจหรือไม่ว่าต้องการเพิ่มประเภทปัญหาใหม่นี้?"}
🤖 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 `@client/src/pages/adminPage/Categories.jsx` around lines 146 - 160, The
ConfirmButton in Categories.jsx always shows add-category text even though
handleSaveCategory also updates existing categories. Make the dialog
title/message dynamic based on whether formData.ticketCtgId is present, so it
shows add wording for new categories and update wording for edits. Use the
existing symbols handleSaveCategory, formData.ticketCtgId, and ConfirmButton to
keep the confirm copy aligned with the actual action.
client/src/pages/adminPage/LocationManagement.jsx-39-63 (1)

39-63: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Enable-guards never trigger — status values use 'active', not 'enable'.

Lines 50 and 55 check value === 'enable', but the corresponding <select> options (Lines 494-495, 511-512, 528-529) only ever emit 'active'/'inactive'. These cascade-prevention branches are unreachable dead code. In practice the disabled props on the selects already block invalid transitions, but this guard is a broken second line of defense.

🐛 Proposed fix
-            if (field === 'roomStatus' && value === 'enable') {
+            if (field === 'roomStatus' && value === 'active') {
                 if (newData.locationStatus === 'inactive' || newData.floorStatus === 'inactive') {
                     return prev;
                 }
             }
-            if (field === 'floorStatus' && value === 'enable') {
+            if (field === 'floorStatus' && value === 'active') {
                 if (newData.locationStatus === 'inactive') {
                     return prev;
                 }
             }
🤖 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 `@client/src/pages/adminPage/LocationManagement.jsx` around lines 39 - 63, The
cascade-prevention checks in handleStatusChange are unreachable because the
status selects only produce 'active' and 'inactive', not 'enable'. Update the
guards in handleStatusChange to match the actual option values emitted by the
locationStatus, floorStatus, and roomStatus selects, or remove the dead branches
if the disabled props already enforce the valid transitions. Use the existing
handleStatusChange logic and the related select fields as the location points
for the fix.
client/src/pages/adminPage/AssetManagement.jsx-328-336 (1)

328-336: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Row highlight never activates from checkbox selection.

selectedEquipments only ever holds strings (see handleSelectOne/handleSelectAll), but this comparison uses item.equipmentId (unconverted) instead of String(item.equipmentId) like the checkbox's checked prop does at line 342. The selected class will never apply when a row is selected via checkbox.

🔧 Suggested fix
-                    className={`data-layout-row ${selectedEquipments.includes(item.equipmentId) || selectedId === item.equipmentId ? 'selected' : ''}`}
+                    className={`data-layout-row ${selectedEquipments.includes(String(item.equipmentId)) || selectedId === item.equipmentId ? 'selected' : ''}`}
🤖 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 `@client/src/pages/adminPage/AssetManagement.jsx` around lines 328 - 336, The
row highlight logic in the table row’s className is comparing selectedEquipments
against item.equipmentId without converting it, while
handleSelectOne/handleSelectAll store strings and the checkbox checked prop
already uses String(item.equipmentId). Update the selected class condition in
AssetManagement.jsx to use the same string form as the checkbox so
checkbox-driven selections can activate the row highlight consistently.
🧹 Nitpick comments (13)
client/src/components/CardFinishProblem.jsx (1)

44-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated SkeletonTheme config across files.

The same baseColor="#ebebeb" highlightColor="#ccc7c7" duration={2} triple is repeated verbatim in CardPendingProblem.jsx and FilterProblem.jsx. Consider extracting a shared AppSkeletonTheme wrapper or a constants file so theme tweaks don't require touching three files.

♻️ Example extraction
+// client/src/components/AppSkeletonTheme.jsx
+import { SkeletonTheme } from 'react-loading-skeleton';
+export const AppSkeletonTheme = ({ children }) => (
+  <SkeletonTheme baseColor="`#ebebeb`" highlightColor="`#ccc7c7`" duration={2}>
+    {children}
+  </SkeletonTheme>
+);
🤖 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 `@client/src/components/CardFinishProblem.jsx` around lines 44 - 70, The
SkeletonTheme configuration is duplicated in SkeletonCardFinishProblem and the
other skeleton components, so extract the shared
baseColor/highlightColor/duration values into a reusable AppSkeletonTheme
wrapper or constants module. Update the skeleton components to consume that
shared theme setup instead of hardcoding the same props in each file, so changes
only need to be made in one place.
client/src/components/CardPendingProblem.css (1)

88-142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Newly added skeleton CSS classes appear unused.

.header-skleton-card, .skleton-img, .skleton-text, .skleton-title-card h3.skleton-text, and .skleton-status are defined here, but CardPendingSkeleton in CardPendingProblem.jsx only applies .skleton-card-pending and reuses the real card's classes (header-card, title-card, operator, location-text, description-text) with react-loading-skeleton's <Skeleton> for the placeholder boxes. These .skleton-* rules are dead code.

🤖 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 `@client/src/components/CardPendingProblem.css` around lines 88 - 142, The
newly added skeleton-specific CSS rules are unused because CardPendingSkeleton
in CardPendingProblem.jsx only uses skleton-card-pending plus the existing real
card class names and react-loading-skeleton placeholders. Remove the dead
.header-skleton-card, .skleton-img, .skleton-text, .skleton-title-card
h3.skleton-text, and .skleton-status selectors from CardPendingProblem.css, or
if they are meant to be used, update CardPendingSkeleton to apply those class
names consistently.
client/src/components/StatisticsSidebar.jsx (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

activeTab/onTabChange props are unused; dead code left in place.

activeTab and onTabChange are accepted but never referenced in the component body — active-link styling is delegated entirely to NavLink. Combined with the commented-out menuItems array, this looks like leftover state from a prior tab-based (non-routed) implementation. Keeping unused props on the public component API is misleading for future maintainers (e.g., Statistics.jsx still wires activeTab/handleTabChange into this component believing it does something).

♻️ Suggested cleanup
-export const StatisticsSidebar = ({ activeTab, onTabChange }) => {
-  // const menuItems = [
-  //   { id: 'statistic-all-prblem', label: 'สถิติปัญหา' },
-  //   { id: 'all', label: 'ประเภทปัญหาที่รับแจ้งมากสุด' },
-  //   { id: 'mine', label: 'สถานที่รับแจ้งมากสุด' },
-  //   { id: 'upvoted', label: 'ปัญหาที่ได้รับ Upvote มากสุด' },
-  // ];
-
+export const StatisticsSidebar = () => {
🤖 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 `@client/src/components/StatisticsSidebar.jsx` around lines 5 - 11, Remove the
unused activeTab and onTabChange props from StatisticsSidebar and update the
component signature and any callers such as Statistics so they no longer pass
tab state that the component does not use. Also clean up the leftover
commented-out menuItems block in StatisticsSidebar to reflect the current
NavLink-based routing implementation and avoid exposing a misleading tab API.
server/controllers/statisticController.js (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused/unrelated import.

create from node:domain is never used, and node:domain is an unrelated legacy Node core module.

🤖 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 `@server/controllers/statisticController.js` at line 1, Remove the unused and
unrelated node:domain import from statisticController since create is never
referenced anywhere in the controller. Update the top-level imports in
statisticController to keep only symbols that are actually used, and verify no
remaining references depend on create or domain.
server/routes/managementRoutes.js (1)

55-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistent route naming convention.

/getTicket-stats mixes camelCase and kebab-case, unlike the sibling /getMostCategoriesOfProblems and /getMostUpvotedTickets routes.

🤖 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 `@server/routes/managementRoutes.js` at line 55, The route path in the
managementRoutes router uses mixed naming conventions, so update the
getTicketStats endpoint to follow the same pattern as the sibling routes and use
one consistent style across these route definitions. Adjust the router.get
registration for getTicketStats so its URL naming matches the existing
getMostCategoriesOfProblems and getMostUpvotedTickets endpoints.
client/src/services/equipmentService.js (1)

5-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unused destructured variable data.

equipmentId and data are destructured but data is never used — the full payload (including equipmentId) is sent instead. Either drop the destructure or use data intentionally in the request body.

🔧 Suggested cleanup
     updateEquipment: async (payload) => {
         try {
-            const { equipmentId, ...data } = payload;
             const response = await axios.put('/api/manage/updateEquipment', payload);
             return response.data;
🤖 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 `@client/src/services/equipmentService.js` around lines 5 - 14, The
`updateEquipment` method destructures `equipmentId` and `data`, but `data` is
never used because the full `payload` is sent in the axios request. Fix this by
either removing the unused destructuring in `updateEquipment` or changing the
request body to use `data` intentionally, and keep the behavior consistent with
the `axios.put('/api/manage/updateEquipment', ...)` call.
client/src/pages/adminPage/AssetManagement.jsx (1)

36-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove leftover debug console.log statements.

Multiple debug logs ('ssss', "เลือก", "Payload แบบ Array...", 'onemode') were left in; clean these up before merging.

Also applies to: 163-163, 201-201, 215-215

🤖 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 `@client/src/pages/adminPage/AssetManagement.jsx` at line 36, Remove the
leftover debug console.log statements from AssetManagement.jsx before merging.
Clean up all the identified logs in the AssetManagement component and related
handlers, including the ones printing EquipmentCtgs, the Thai debug messages,
the payload array message, and the onemode value, so no temporary debugging
output remains in the final code.
client/src/hooks/useEquipment.js (1)

33-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unused updateEquipment export from useEquipment
No current caller uses it, and AssetManagement.jsx updates through equipmentService.updateEquipment instead, so this extra PATCH path is redundant and can drift from the active PUT contract.

🤖 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 `@client/src/hooks/useEquipment.js` around lines 33 - 40, The useEquipment hook
still defines an unused updateEquipment callback with its own PATCH request, but
the active update flow is handled elsewhere through
equipmentService.updateEquipment. Remove the updateEquipment function/export
from useEquipment and clean up any related references so the hook only exposes
the APIs that are actually consumed, keeping the hook aligned with the current
update contract.
client/src/pages/adminPage/AssetManagement.css (2)

152-156: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate .layout-table selector.

.layout-table is declared twice with disjoint rule sets; consider merging into a single block for clarity.

Also applies to: 175-177

🤖 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 `@client/src/pages/adminPage/AssetManagement.css` around lines 152 - 156, The
`.layout-table` selector in AssetManagement.css is duplicated with separate rule
blocks, so merge the duplicated styles into one consolidated `.layout-table`
definition. Update the CSS near the existing `.layout-table` rules to combine
all properties currently split across the two blocks, and remove the redundant
declaration so the table styles live in a single place.

135-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scrollbar hidden only for Firefox/legacy Edge/IE, not Chromium/WebKit.

-ms-overflow-style and scrollbar-width cover Firefox/old Edge/IE, but Chrome/Safari/new Edge still render the native scrollbar since ::-webkit-scrollbar isn't targeted.

🎨 Suggested addition
 .table-responsive-wrapper {
   -ms-overflow-style: none;
   /* ซ่อนใน IE และ Edge */
   scrollbar-width: none;
   /* ซ่อนใน Firefox */
+}
+.table-responsive-wrapper::-webkit-scrollbar {
+  display: none;
+  /* ซ่อนใน Chrome, Safari, Edge (Chromium) */
🤖 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 `@client/src/pages/adminPage/AssetManagement.css` around lines 135 - 149, The
scrollbar-hiding styles in the .table-responsive-wrapper rule only cover Firefox
and legacy IE/Edge, so Chromium/WebKit browsers still show the native scrollbar.
Update the AssetManagement.css styles for .table-responsive-wrapper to also
target the WebKit scrollbar pseudo-element so Chrome, Safari, and new Edge hide
it consistently alongside the existing -ms-overflow-style and scrollbar-width
rules.
client/src/components/componentsAdmin/ImportEquipments.jsx (1)

103-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New error modal lacks dialog accessibility semantics.

The overlay/content div has no role="dialog", aria-modal="true", initial focus management, or Escape-to-close handling, so keyboard and screen-reader users have no clear entry/exit from this newly introduced full-screen modal.

♻️ Suggested addition
             {errorList.length > 0 && (
-                <div className="custom-modal-overlay">
-                    <div className="custom-modal-content">
+                <div className="custom-modal-overlay" role="presentation">
+                    <div className="custom-modal-content" role="dialog" aria-modal="true" aria-labelledby="import-error-title">
🤖 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 `@client/src/components/componentsAdmin/ImportEquipments.jsx` around lines 103
- 131, The new error modal in ImportEquipments lacks basic dialog accessibility,
so update the modal overlay/content to behave like a true dialog. Add dialog
semantics and focus handling in the same component that renders errorList,
including a dialog role, aria-modal, a label tied to the header, initial focus
when it opens, and Escape-to-close support that reuses the existing
setErrorList([]) close behavior.
server/controllers/EquipmentControllers.js (1)

220-242: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Partial-success response drops insertion count.

When errors.length > 0, the response only returns { errors } even though validDataToInsert may have already been persisted via createMany above. The client has no way to know how many rows actually succeeded before the errors occurred, only that "something went wrong."

♻️ Suggested addition
         if (errors.length > 0) {
             return res.status(200).json({
-                errors: errors
+                errors: errors,
+                insertedCount: validDataToInsert.length
             });
         }
🤖 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 `@server/controllers/EquipmentControllers.js` around lines 220 - 242, The
partial-success path in the equipment upload handler loses the count of rows
that were actually persisted because the `errors.length > 0` response only
returns `errors`. Update the upload flow in `EquipmentControllers` so the
response in that branch also includes the number of successful inserts from
`validDataToInsert` (and, if helpful, a success message), matching the
information already returned in the full-success path. Make sure the
`createMany` result handling and the final `res.status(200).json(...)` response
clearly communicate both successes and failures.
client/src/components/componentsAdmin/ImportEquipments.css (1)

161-161: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename popIn keyframes to kebab-case.

Stylelint flags this as violating keyframes-name-pattern.

🎨 Suggested fix
-    animation: popIn 0.3s ease-out forwards; /* เพิ่มลูกเล่นแอนิเมชันเด้งเข้ามา */
+    animation: pop-in 0.3s ease-out forwards; /* เพิ่มลูกเล่นแอนิเมชันเด้งเข้ามา */
-@keyframes popIn {
+@keyframes pop-in {

Also applies to: 208-210

🤖 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 `@client/src/components/componentsAdmin/ImportEquipments.css` at line 161, The
`popIn` animation name in `ImportEquipments.css` violates the
`keyframes-name-pattern` rule, so rename the `@keyframes popIn` definition to a
kebab-case name and update every `animation: popIn ...` reference in the same
stylesheet to match, including the occurrences near the later keyframes block.
Use the existing `popIn` animation usage and keyframes declaration as the
symbols to find and replace consistently.

Source: Linters/SAST tools

🤖 Prompt for all review comments with 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.

Inline comments:
In `@client/src/components/CardPendingProblem.css`:
- Around line 20-25: The image styling in the CardPendingProblem CSS is too
broad because the `.img-card, img` selector also targets every bare img on the
page. Update the rule in `CardPendingProblem.css` to scope the
object-fit/object-position/size styles only to `.img-card`, so other images like
the Dashboard empty-state image are not affected.

In `@client/src/hooks/useImportEquipments.js`:
- Around line 45-59: The error handling in useImportEquipments is missing
backend responses that return a singular error field, so users get no feedback
on failures. Update the catch block to check error.response.data.error in
addition to errors and message, and surface it with alert or equivalent so
uploadEquipments failures from EquipmentControllers.js are shown to the user;
keep the existing setErrorList behavior for validation errors and use the same
response parsing flow in useImportEquipments.
- Around line 3-9: `useImportEquipments` is creating its own `useEquipment()`
instance, so the refresh it triggers won’t update the table in
`AssetManagement.jsx`; change `useImportEquipments`/`ImportEquipments` to
receive the parent `refetch` callback as an argument and call that after a
successful upload instead of the local hook instance. Also update the upload
error handling in `useImportEquipments` so the catch block reads and surfaces
`error.response.data.error` (when present) from `uploadEquipments`, ensuring
400/500 responses show a user-facing message instead of being swallowed.

In `@client/src/hooks/useStatistics.js`:
- Around line 7-10: The initial ticketStats state in useStatistics.js is sized
for the weekly view, but Statistics.jsx defaults to the all-months view, causing
the chart data length to mismatch the categories before fetch completes. Update
the initial state in useStatistics so created and resolved default to 12-item
arrays (or otherwise match the all-months display used by Statistics and its
ticketStats?.created/ticketStats?.resolved fallback logic) so the first render
aligns with chartCategories.

In `@client/src/pages/adminPage/AssetManagement.jsx`:
- Around line 24-25: The delete confirmation flow in AssetManagement is broken
because the confirm dialog is commented out and the delete state shape is
inconsistent. Restore the ConfirmButton block so the “ลบ” action can open a
visible confirmation dialog and wire its onConfirm to handleDelete, and change
isDeleteConfirmOpen to use a boolean consistently with its setter/getter usage
so ConfirmButton’s isOpen guard works correctly.
- Around line 178-204: The bulk-update branch in AssetManagement.jsx currently
builds a payload and shows success without actually persisting anything because
the update call is commented out. Add the real bulk update flow by implementing
equipmentService.updateMultipleEquipments and wiring it to the matching backend
endpoint, then call it from the isBulkMode path before setSuccess runs. If the
backend/API is not ready yet, disable or hide the bulk-edit path so the UI does
not report a false success.
- Around line 59-72: The single-select prefill in AssetManagement.jsx is reading
locationId, floorId, and roomId from the wrong object shape, so those fields can
stay empty and later submit invalid numeric values. Update the
selectedItem-to-formData mapping in the single-selection branch to derive these
values from the nested room/floor/location structure used elsewhere in this page
(the same shape coming from equipment), and make sure setFormData always
receives the correctly resolved ids before submit.

In `@client/src/pages/adminPage/Categories.jsx`:
- Around line 54-81: The handleSaveCategory flow in Categories.jsx includes an
unnecessary artificial 1.5s delay before calling IssueCategoryService, which
slows every save without benefit. Remove the setTimeout-based await from
handleSaveCategory so the save goes directly into the
addIssueCategoryApi/updateIssueCategoryApi call path, then keep the existing
success, fetchCategories, and form reset logic unchanged.

In `@client/src/pages/adminPage/LocationManagement.jsx`:
- Around line 247-253: `handleConfirmSaveStatus` is validating the wrong
`formData` fields and opening the confirm dialog without any message. Update the
guard in `LocationManagement` to use the actual state keys for
location/floor/room selection (the same keys used throughout the form state),
and when calling `setConfirmSaveStatus`, include the dialog text so
`ConfirmButton` can render a non-empty `confirmSaveStatus.message`.
- Around line 179-203: Remove the hardcoded 1.5s artificial delay from the
add/save flows so the UI does not pause unnecessarily. In
LocationManagement.jsx, update handleAddNewfloor, handleAddNewLocation, and
handleSaveStatus to proceed directly with their existing payload/service logic
instead of awaiting setTimeout, and keep their loading/error/success handling
unchanged. This will align them with handleAddNewRoom and make all add/save
actions consistent.
- Around line 243-246: The cancel flow in LocationManagement is broken because
handleCancelSubmit references a non-existent setConfirmSubmit state setter, and
the adjacent “ยกเลิก” button is not wired to any handler. Update
handleCancelSubmit to use the correct existing confirm state or remove it if
unnecessary, and attach the cancel button to the proper cancel handler so it
closes the dialog and resets the form. Use the existing confirmSubmitLocation,
confirmSubmitFloor, confirmSubmitRoom, and confirmSaveStatus state patterns to
locate the right confirmation state to update.

In `@client/src/pages/DetailTicket.jsx`:
- Around line 95-102: The “see more” trigger in DetailTicket is missing visible
label text, so the clickable paragraph is undiscoverable; update the
`see-more-text` elements in the before/after image sections to render clear text
inside the `<p>` while keeping the existing `onClick` behavior and styling, and
make sure both the before-images and after-images variants are fixed
consistently.

In `@client/src/pages/Statistics.jsx`:
- Around line 78-169: The nested route in Statistics is using an absolute path
for top-categories, so it will not resolve under the parent /statistics route
like the sidebar expects. Update the Route for top-categories in Statistics.jsx
to use a relative path, matching the existing routing setup used by Routes/Route
and the /statistics/top-locations entry, so the tab renders correctly.

In `@server/controllers/managementControllers.js`:
- Around line 449-471: The updateRoleUsers handler is logging the full req.user
token payload, which can leak sensitive user data into server logs. Remove the
console.log that prints the entire req.user object and keep only minimal,
non-sensitive debugging output if needed, using updateRoleUsers and myUserId as
the key places to adjust. Ensure the role update flow still validates self-edits
and continues to log only safe identifiers such as userId when necessary.
- Around line 459-465: The userRole value is being written directly through
prisma.user.update without any server-side validation, so add an allowlist check
before updating the user. In the management controller that handles this update,
validate req.body.userRole against the permitted role values used by the app,
reject invalid input with an error response, and only pass the sanitized role
into the update call. Keep the fix localized around the userRole handling in
this controller so role-based checks elsewhere remain consistent.
- Around line 59-77: Add a real uniqueness guarantee for locationName instead of
relying on the findFirst check in managementControllers.js. Update the Prisma
model in schema.prisma to mark locationName as unique, generate/apply the
migration so the database enforces it, and then in the create flow around
prisma.location.create handle Prisma error P2002 by returning the same 400-style
duplicate-location response.

In `@server/controllers/statisticController.js`:
- Around line 6-19: The Prisma model accessors in the statistics controller are
using the wrong casing, which will break at runtime because the generated client
exposes camelCase names. Update the queries in statisticController to use
prisma.ticketCategory (and prisma.ticket where applicable) instead of
prisma.TicketCategory / prisma.Ticket, keeping the same include and orderBy
logic.
- Around line 36-50: The Prisma delegate names in statisticController are using
the wrong casing and will fail at runtime. Update the queries in the relevant
controller method to use the generated lowercase delegates on prisma, replacing
prisma.Ticket with prisma.ticket and any prisma.TicketCategory usage with
prisma.ticketCategory, while keeping the existing findMany and related
include/orderBy logic unchanged.

---

Outside diff comments:
In `@client/src/App.jsx`:
- Around line 44-51: The authenticated route set in App’s <Routes> is missing
the previous catch-all redirect, so unmatched logged-in paths now fall through
with no render. Restore a wildcard route in the authenticated branch using the
existing routing pattern in App.jsx, and make it redirect to the home/dashboard
route the same way the logged-out branch handles unknown paths. Keep the new
/statistics/* route intact while ensuring all other unmatched authenticated
paths are covered.

In `@client/src/hooks/useUsers.js`:
- Around line 7-16: The initial mount-time call to fetchUsers in useUsers is now
unsafe because fetchUsers rethrows errors, so the useEffect-triggered call can
produce an unhandled promise rejection. Update the useEffect that invokes
fetchUsers to handle the returned promise explicitly, either by awaiting it
inside an async helper with try/catch or by attaching a .catch handler, while
keeping fetchUsers’ current error propagation behavior intact.

In `@client/src/pages/DetailTicket.jsx`:
- Around line 21-41: DetailTicket.jsx only handles a subset of ticket statuses,
so canceled, rejected, and duplicate can render with empty labels and no
matching styling. Update the statusLabels mapping in DetailTicket.jsx to include
those missing statuses or add a safe fallback label, and make sure
DetailTicket.css includes matching .ticketStatus classes for each status used by
ticket.ticketStatus.

---

Minor comments:
In `@client/src/hooks/useStatistics.js`:
- Around line 20-27: The fetchMostUpvotedTickets logic in useStatistics should
guard against missing response.data.data before updating state, since setting it
to undefined causes Statistics.jsx to fail when it calls .map(). Update the
axios response handling in fetchMostUpvotedTickets so it always stores a safe
array fallback when the backend payload shape is absent or changed, and keep the
error handling in the same function consistent with that fallback.

In `@client/src/pages/adminPage/AssetManagement.jsx`:
- Around line 328-336: The row highlight logic in the table row’s className is
comparing selectedEquipments against item.equipmentId without converting it,
while handleSelectOne/handleSelectAll store strings and the checkbox checked
prop already uses String(item.equipmentId). Update the selected class condition
in AssetManagement.jsx to use the same string form as the checkbox so
checkbox-driven selections can activate the row highlight consistently.

In `@client/src/pages/adminPage/Categories.jsx`:
- Around line 146-160: The ConfirmButton in Categories.jsx always shows
add-category text even though handleSaveCategory also updates existing
categories. Make the dialog title/message dynamic based on whether
formData.ticketCtgId is present, so it shows add wording for new categories and
update wording for edits. Use the existing symbols handleSaveCategory,
formData.ticketCtgId, and ConfirmButton to keep the confirm copy aligned with
the actual action.

In `@client/src/pages/adminPage/LocationManagement.jsx`:
- Around line 39-63: The cascade-prevention checks in handleStatusChange are
unreachable because the status selects only produce 'active' and 'inactive', not
'enable'. Update the guards in handleStatusChange to match the actual option
values emitted by the locationStatus, floorStatus, and roomStatus selects, or
remove the dead branches if the disabled props already enforce the valid
transitions. Use the existing handleStatusChange logic and the related select
fields as the location points for the fix.

In `@client/src/pages/adminPage/UserManagement.jsx`:
- Around line 41-70: The role update flow in UserManagement.jsx only checks
formData.userRole, so handleConfirmUpdateRole can proceed even when no table row
is selected and selectedId is null. Update handleConfirmUpdateRole to also
validate that a user row has been selected before opening confirmSubmit, and
show a clear client-side error if selectedId is missing. Use the existing
selectedId, handleConfirmUpdateRole, and handleUpdateRole flow to prevent
calling updateRoleUser with an invalid userId.

In `@server/controllers/statisticController.js`:
- Around line 64-120: The getTicketStats controller currently falls into the
month-specific branch even when req.query.month is missing, causing
parseInt(month) to become NaN and the Prisma date range to use Invalid Date.
Update getTicketStats to validate the month input before building the date
window: if month is absent or not a valid numeric month, either default to the
current month or return a clear 400 response, and keep the existing month ===
'all' path unchanged.

---

Nitpick comments:
In `@client/src/components/CardFinishProblem.jsx`:
- Around line 44-70: The SkeletonTheme configuration is duplicated in
SkeletonCardFinishProblem and the other skeleton components, so extract the
shared baseColor/highlightColor/duration values into a reusable AppSkeletonTheme
wrapper or constants module. Update the skeleton components to consume that
shared theme setup instead of hardcoding the same props in each file, so changes
only need to be made in one place.

In `@client/src/components/CardPendingProblem.css`:
- Around line 88-142: The newly added skeleton-specific CSS rules are unused
because CardPendingSkeleton in CardPendingProblem.jsx only uses
skleton-card-pending plus the existing real card class names and
react-loading-skeleton placeholders. Remove the dead .header-skleton-card,
.skleton-img, .skleton-text, .skleton-title-card h3.skleton-text, and
.skleton-status selectors from CardPendingProblem.css, or if they are meant to
be used, update CardPendingSkeleton to apply those class names consistently.

In `@client/src/components/componentsAdmin/ImportEquipments.css`:
- Line 161: The `popIn` animation name in `ImportEquipments.css` violates the
`keyframes-name-pattern` rule, so rename the `@keyframes popIn` definition to a
kebab-case name and update every `animation: popIn ...` reference in the same
stylesheet to match, including the occurrences near the later keyframes block.
Use the existing `popIn` animation usage and keyframes declaration as the
symbols to find and replace consistently.

In `@client/src/components/componentsAdmin/ImportEquipments.jsx`:
- Around line 103-131: The new error modal in ImportEquipments lacks basic
dialog accessibility, so update the modal overlay/content to behave like a true
dialog. Add dialog semantics and focus handling in the same component that
renders errorList, including a dialog role, aria-modal, a label tied to the
header, initial focus when it opens, and Escape-to-close support that reuses the
existing setErrorList([]) close behavior.

In `@client/src/components/StatisticsSidebar.jsx`:
- Around line 5-11: Remove the unused activeTab and onTabChange props from
StatisticsSidebar and update the component signature and any callers such as
Statistics so they no longer pass tab state that the component does not use.
Also clean up the leftover commented-out menuItems block in StatisticsSidebar to
reflect the current NavLink-based routing implementation and avoid exposing a
misleading tab API.

In `@client/src/hooks/useEquipment.js`:
- Around line 33-40: The useEquipment hook still defines an unused
updateEquipment callback with its own PATCH request, but the active update flow
is handled elsewhere through equipmentService.updateEquipment. Remove the
updateEquipment function/export from useEquipment and clean up any related
references so the hook only exposes the APIs that are actually consumed, keeping
the hook aligned with the current update contract.

In `@client/src/pages/adminPage/AssetManagement.css`:
- Around line 152-156: The `.layout-table` selector in AssetManagement.css is
duplicated with separate rule blocks, so merge the duplicated styles into one
consolidated `.layout-table` definition. Update the CSS near the existing
`.layout-table` rules to combine all properties currently split across the two
blocks, and remove the redundant declaration so the table styles live in a
single place.
- Around line 135-149: The scrollbar-hiding styles in the
.table-responsive-wrapper rule only cover Firefox and legacy IE/Edge, so
Chromium/WebKit browsers still show the native scrollbar. Update the
AssetManagement.css styles for .table-responsive-wrapper to also target the
WebKit scrollbar pseudo-element so Chrome, Safari, and new Edge hide it
consistently alongside the existing -ms-overflow-style and scrollbar-width
rules.

In `@client/src/pages/adminPage/AssetManagement.jsx`:
- Line 36: Remove the leftover debug console.log statements from
AssetManagement.jsx before merging. Clean up all the identified logs in the
AssetManagement component and related handlers, including the ones printing
EquipmentCtgs, the Thai debug messages, the payload array message, and the
onemode value, so no temporary debugging output remains in the final code.

In `@client/src/services/equipmentService.js`:
- Around line 5-14: The `updateEquipment` method destructures `equipmentId` and
`data`, but `data` is never used because the full `payload` is sent in the axios
request. Fix this by either removing the unused destructuring in
`updateEquipment` or changing the request body to use `data` intentionally, and
keep the behavior consistent with the `axios.put('/api/manage/updateEquipment',
...)` call.

In `@server/controllers/EquipmentControllers.js`:
- Around line 220-242: The partial-success path in the equipment upload handler
loses the count of rows that were actually persisted because the `errors.length
> 0` response only returns `errors`. Update the upload flow in
`EquipmentControllers` so the response in that branch also includes the number
of successful inserts from `validDataToInsert` (and, if helpful, a success
message), matching the information already returned in the full-success path.
Make sure the `createMany` result handling and the final
`res.status(200).json(...)` response clearly communicate both successes and
failures.

In `@server/controllers/statisticController.js`:
- Line 1: Remove the unused and unrelated node:domain import from
statisticController since create is never referenced anywhere in the controller.
Update the top-level imports in statisticController to keep only symbols that
are actually used, and verify no remaining references depend on create or
domain.

In `@server/routes/managementRoutes.js`:
- Line 55: The route path in the managementRoutes router uses mixed naming
conventions, so update the getTicketStats endpoint to follow the same pattern as
the sibling routes and use one consistent style across these route definitions.
Adjust the router.get registration for getTicketStats so its URL naming matches
the existing getMostCategoriesOfProblems and getMostUpvotedTickets endpoints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00ce7fb4-ff4d-4caf-85eb-1c5c0f5275c7

📥 Commits

Reviewing files that changed from the base of the PR and between fa3c512 and 365094b.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (40)
  • client/package.json
  • client/src/App.jsx
  • client/src/components/CardFinishProblem.jsx
  • client/src/components/CardPendingProblem.css
  • client/src/components/CardPendingProblem.jsx
  • client/src/components/ChartForStatistic.jsx
  • client/src/components/FilterProblem.jsx
  • client/src/components/StatisticsSidebar.css
  • client/src/components/StatisticsSidebar.jsx
  • client/src/components/componentsAdmin/ImportEquipments.css
  • client/src/components/componentsAdmin/ImportEquipments.jsx
  • client/src/components/componentsStyles/CardFinishProblem.css
  • client/src/hooks/useEquipment.js
  • client/src/hooks/useEquiptmentCtg.js
  • client/src/hooks/useImportEquipments.js
  • client/src/hooks/useStatistics.js
  • client/src/hooks/useTickets.js
  • client/src/hooks/useUsers.js
  • client/src/index.css
  • client/src/pages/AddIssue.jsx
  • client/src/pages/Dashboard.jsx
  • client/src/pages/DetailTicket.css
  • client/src/pages/DetailTicket.jsx
  • client/src/pages/Statistics.css
  • client/src/pages/Statistics.jsx
  • client/src/pages/Tracking.css
  • client/src/pages/adminPage/AssetManagement.css
  • client/src/pages/adminPage/AssetManagement.jsx
  • client/src/pages/adminPage/Categories.css
  • client/src/pages/adminPage/Categories.jsx
  • client/src/pages/adminPage/LocationManagement.jsx
  • client/src/pages/adminPage/UserManagement.jsx
  • client/src/services/equipmentService.js
  • client/src/utils/timeline.js
  • server/controllers/EquipmentControllers.js
  • server/controllers/managementControllers.js
  • server/controllers/statisticController.js
  • server/controllers/ticketControllers.js
  • server/routes/managementRoutes.js
  • server/routes/ticketRoutes.js
💤 Files with no reviewable changes (1)
  • client/src/pages/AddIssue.jsx

Comment on lines 20 to +25
.img-card,
img {
object-fit: cover;
object-position: center;
width: 150px;
height: 150px;
height: 160px;

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== File list ==\n'
git ls-files 'client/src/components/CardPendingProblem.css' 'client/src/**/Dashboard.jsx' 'client/src/**/Dashboard.tsx' 'client/src/**/Dashboard.js' 'client/src/**/Dashboard.ts' || true

printf '\n== CSS file ==\n'
cat -n client/src/components/CardPendingProblem.css

printf '\n== Search for stylesheet imports and bare img usage ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'CardPendingProblem\.css|<img\b|className=.*img-card|img-card' client/src

printf '\n== Relevant Dashboard file(s) ==\n'
for f in $(git ls-files 'client/src/**/Dashboard.jsx' 'client/src/**/Dashboard.tsx' 'client/src/**/Dashboard.js' 'client/src/**/Dashboard.ts' | head -n 5); do
  echo "--- $f ---"
  cat -n "$f"
done

Repository: CNPJB/trouble-tracking-system-project

Length of output: 13481


Scope the image rule to .img-card
.img-card, img { ... } is a selector list, so the img part applies to every bare <img> on pages that load this stylesheet. That also affects the empty-state image in Dashboard.jsx.

🐛 Proposed fix
-.img-card,
-img {
+.img-card img {
     object-fit: cover;
     object-position: center;
     width: 150px;
     height: 160px;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.img-card,
img {
object-fit: cover;
object-position: center;
width: 150px;
height: 150px;
height: 160px;
.img-card img {
object-fit: cover;
object-position: center;
width: 150px;
height: 160px;
}
🤖 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 `@client/src/components/CardPendingProblem.css` around lines 20 - 25, The image
styling in the CardPendingProblem CSS is too broad because the `.img-card, img`
selector also targets every bare img on the page. Update the rule in
`CardPendingProblem.css` to scope the object-fit/object-position/size styles
only to `.img-card`, so other images like the Dashboard empty-state image are
not affected.

Comment on lines +3 to +9
// hooks
import { useEquipment } from '../hooks/useEquipment'
import { useLoadingState } from '../hooks/useLoadingState'

export const useImportEquipments = (url) => {
const { loading, reset } = useLoadingState();
const { refetch } = useEquipment();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm AssetManagement.jsx independently calls useEquipment() and uses `equipment`/`refetch` from it
rg -n "useEquipment\(" client/src/pages/adminPage/AssetManagement.jsx client/src/hooks/useImportEquipments.js
rg -n "<ImportEquipments" client/src/pages/adminPage/AssetManagement.jsx

Repository: CNPJB/trouble-tracking-system-project

Length of output: 479


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant files first
ast-grep outline client/src/hooks/useEquipment.js --view expanded || true
ast-grep outline client/src/hooks/useImportEquipments.js --view expanded || true
ast-grep outline client/src/pages/adminPage/AssetManagement.jsx --view expanded || true

# Read the relevant sections
printf '\n--- useEquipment.js ---\n'
wc -l client/src/hooks/useEquipment.js
sed -n '1,220p' client/src/hooks/useEquipment.js

printf '\n--- useImportEquipments.js ---\n'
wc -l client/src/hooks/useImportEquipments.js
sed -n '1,240p' client/src/hooks/useImportEquipments.js

printf '\n--- AssetManagement.jsx (relevant slices) ---\n'
wc -l client/src/pages/adminPage/AssetManagement.jsx
sed -n '1,120p' client/src/pages/adminPage/AssetManagement.jsx
sed -n '460,540p' client/src/pages/adminPage/AssetManagement.jsx

Repository: CNPJB/trouble-tracking-system-project

Length of output: 10797


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- ImportEquipments component ---\n'
fd -a "ImportEquipments.*" client/src
if [ -f client/src/components/componentsAdmin/ImportEquipments.jsx ]; then
  wc -l client/src/components/componentsAdmin/ImportEquipments.jsx
  sed -n '1,220p' client/src/components/componentsAdmin/ImportEquipments.jsx
fi

printf '\n--- Search for useImportEquipments usage ---\n'
rg -n "useImportEquipments\(" client/src

printf '\n--- Search for uploadEquipments endpoint / error shapes ---\n'
rg -n "uploadEquipments|No file uploaded|errors:|error:" client/src server . || true

Repository: CNPJB/trouble-tracking-system-project

Length of output: 21935


Pass refetch from AssetManagement.jsx into ImportEquipments.

  • useImportEquipments creates its own useEquipment() instance, so refetch() here only updates that hook’s local state and won’t refresh the table the admin sees.
  • The upload catch block also ignores error.response.data.error, so 400/500 responses from uploadEquipments can fail without a user-facing message.
🤖 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 `@client/src/hooks/useImportEquipments.js` around lines 3 - 9,
`useImportEquipments` is creating its own `useEquipment()` instance, so the
refresh it triggers won’t update the table in `AssetManagement.jsx`; change
`useImportEquipments`/`ImportEquipments` to receive the parent `refetch`
callback as an argument and call that after a successful upload instead of the
local hook instance. Also update the upload error handling in
`useImportEquipments` so the catch block reads and surfaces
`error.response.data.error` (when present) from `uploadEquipments`, ensuring
400/500 responses show a user-facing message instead of being swallowed.

Comment on lines 45 to +59
} catch (error) {
console.error(error);
if (error.response && error.response.data.errors) {
setErrorList(error.response.data.errors);
console.error("Upload Error:", error);

if (error.response && error.response.data) {
if (error.response.data.errors) {
setErrorList(error.response.data.errors);
} else if (error.response.data.message) {
alert(error.response.data.message);
}
} else {
alert("เกิดข้อผิดพลาดในการอัปโหลด");
alert("เกิดข้อผิดพลาดในการรับส่งข้อมูลกับเซิร์ฟเวอร์");
}

return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Some backend failure responses use error (singular), which this handler never checks — silent failure for the user.

EquipmentControllers.js's uploadEquipments returns { error: 'No file uploaded' } (400) and { error: 'Failed to upload equipment data' } (500) on failure paths, but this catch block only inspects error.response.data.errors and error.response.data.message. For those two response shapes, neither branch matches, so no alert() fires and errorList stays empty — the user sees the submit button simply re-enable with zero feedback.

🐛 Suggested fix
       if (error.response && error.response.data) {
         if (error.response.data.errors) {
           setErrorList(error.response.data.errors);
         } else if (error.response.data.message) {
           alert(error.response.data.message);
+        } else if (error.response.data.error) {
+          alert(error.response.data.error);
         }
       } else {
         alert("เกิดข้อผิดพลาดในการรับส่งข้อมูลกับเซิร์ฟเวอร์");
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.error(error);
if (error.response && error.response.data.errors) {
setErrorList(error.response.data.errors);
console.error("Upload Error:", error);
if (error.response && error.response.data) {
if (error.response.data.errors) {
setErrorList(error.response.data.errors);
} else if (error.response.data.message) {
alert(error.response.data.message);
}
} else {
alert("เกิดข้อผิดพลาดในการอัปโหลด");
alert("เกิดข้อผิดพลาดในการรับส่งข้อมูลกับเซิร์ฟเวอร์");
}
return false;
} catch (error) {
console.error("Upload Error:", error);
if (error.response && error.response.data) {
if (error.response.data.errors) {
setErrorList(error.response.data.errors);
} else if (error.response.data.message) {
alert(error.response.data.message);
} else if (error.response.data.error) {
alert(error.response.data.error);
}
} else {
alert("เกิดข้อผิดพลาดในการรับส่งข้อมูลกับเซิร์ฟเวอร์");
}
return false;
🤖 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 `@client/src/hooks/useImportEquipments.js` around lines 45 - 59, The error
handling in useImportEquipments is missing backend responses that return a
singular error field, so users get no feedback on failures. Update the catch
block to check error.response.data.error in addition to errors and message, and
surface it with alert or equivalent so uploadEquipments failures from
EquipmentControllers.js are shown to the user; keep the existing setErrorList
behavior for validation errors and use the same response parsing flow in
useImportEquipments.

Comment on lines +7 to +10
const [ticketStats, setTicketStats] = useState({
created: [0, 0, 0, 0, 0],
resolved: [0, 0, 0, 0, 0]
});

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 | 🟠 Major | ⚡ Quick win

Initial ticketStats shape mismatches the default "all months" view.

The default UI state (selectedMonth = 'all' in Statistics.jsx) expects up to 12 monthly data points, but the initial state here only provides 5-element arrays (sized for the weekly view). Since Statistics.jsx does (ticketStats?.created || Array(12).fill(0)).slice(0, displayCount), the truthy 5-item default is used instead of a 12-item fallback, so on first render (before the fetch resolves) finalCreated/finalResolved will have length 5 while chartCategories has up to 12 entries — a data/category length mismatch fed into ApexCharts.

🩹 Suggested fix
     const [ticketStats, setTicketStats] = useState({
-        created: [0, 0, 0, 0, 0],
-        resolved: [0, 0, 0, 0, 0]
+        created: Array(12).fill(0),
+        resolved: Array(12).fill(0)
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [ticketStats, setTicketStats] = useState({
created: [0, 0, 0, 0, 0],
resolved: [0, 0, 0, 0, 0]
});
const [ticketStats, setTicketStats] = useState({
created: Array(12).fill(0),
resolved: Array(12).fill(0)
});
🤖 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 `@client/src/hooks/useStatistics.js` around lines 7 - 10, The initial
ticketStats state in useStatistics.js is sized for the weekly view, but
Statistics.jsx defaults to the all-months view, causing the chart data length to
mismatch the categories before fetch completes. Update the initial state in
useStatistics so created and resolved default to 12-item arrays (or otherwise
match the all-months display used by Statistics and its
ticketStats?.created/ticketStats?.resolved fallback logic) so the first render
aligns with chartCategories.

Comment on lines +24 to +25
const [isUpdateConfirmOpen, setIsUpdateConfirmOpen] = useState({ isOpen: false });
const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState({ isOpen: false });

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 | 🔴 Critical | ⚡ Quick win

Delete confirmation flow is non-functional; state shape also mismatched.

Two issues:

  1. The ConfirmButton for delete is fully commented out, so clicking "ลบ" only calls setIsDeleteConfirmOpen(true) with no visible dialog rendered anywhere — handleDelete (and thus deleting equipment) is now unreachable from the UI.
  2. isDeleteConfirmOpen is initialized as { isOpen: false } (an object, always truthy) but everywhere it's actually set/read as a boolean (setIsDeleteConfirmOpen(true), setIsDeleteConfirmOpen(false), isOpen={isDeleteConfirmOpen}). If the commented block is restored as-is, ConfirmButton's if (!isOpen) return null; guard would never block rendering because the initial object is truthy.
🔧 Suggested fix
-  const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState({ isOpen: false });
+  const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false);

And restore the ConfirmButton block wiring onConfirm={handleDelete}.

Also applies to: 488-503

🤖 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 `@client/src/pages/adminPage/AssetManagement.jsx` around lines 24 - 25, The
delete confirmation flow in AssetManagement is broken because the confirm dialog
is commented out and the delete state shape is inconsistent. Restore the
ConfirmButton block so the “ลบ” action can open a visible confirmation dialog
and wire its onConfirm to handleDelete, and change isDeleteConfirmOpen to use a
boolean consistently with its setter/getter usage so ConfirmButton’s isOpen
guard works correctly.

Comment on lines +59 to +77
if (!locationName || locationName.trim() === '') {
return res.status(400).json({ error: 'กรุณาระบุชื่อสถานที่' });
}

const cleanLocationName = locationName.trim();

const findLocation = await prisma.location.findFirst({
where: {
locationName: cleanLocationName
}
});

if (findLocation) {
return res.status(400).json({ error: 'สถานที่นี้มีอยู่แล้วในระบบ' });
}

const location = await prisma.location.create({
data: {
locationName,
locationName: cleanLocationName,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e prisma | xargs -I{} rg -n -B2 -A2 'locationName' {}

Repository: CNPJB/trouble-tracking-system-project

Length of output: 517


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate schema and migrations mentioning location_name / Location / P2002
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
  -e 'model Location' \
  -e 'location_name' \
  -e '`@unique`' \
  -e 'P2002' \
  prisma server . 2>/dev/null || true

printf '\n--- schema slice ---\n'
schema_file="$(fd -a -e prisma . | head -n 1 || true)"
if [ -n "${schema_file:-}" ]; then
  sed -n '20,45p' "$schema_file"
fi

printf '\n--- migrations mentioning location_name ---\n'
fd -a -t f prisma migrations . 2>/dev/null | while read -r f; do
  rg -n 'location_name|locationName|UNIQUE|unique' "$f" && echo "--- $f ---"
done

Repository: CNPJB/trouble-tracking-system-project

Length of output: 3570


Add a unique constraint for locationName. findFirst + create still allows duplicate locations under concurrency because server/prisma/schema.prisma doesn’t declare locationName as @unique, and the migration doesn’t enforce it either. Add the DB constraint and handle P2002 here as a 400.

🤖 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 `@server/controllers/managementControllers.js` around lines 59 - 77, Add a real
uniqueness guarantee for locationName instead of relying on the findFirst check
in managementControllers.js. Update the Prisma model in schema.prisma to mark
locationName as unique, generate/apply the migration so the database enforces
it, and then in the create flow around prisma.location.create handle Prisma
error P2002 by returning the same 400-style duplicate-location response.

Comment on lines +449 to +471
export const updateRoleUsers = async (req, res) => {
try {
const { userId, userRole } = req.body;

const myUserId = req.user.userId;
console.log("ID จากหน้าบ้าน:", userId);
console.log("ข้อมูลใน Token ของฉัน:", req.user);
if (String(userId) === String(myUserId)) {
return res.status(400).json({ error: "ไม่อนุญาตให้แก้ไขสิทธิ์ของตัวเอง" });
}
const users = await prisma.user.update({
where: {
userId: Number(userId)
}, data: {
role: userRole
}
});
res.status(200).json(users);
} catch (error) {
console.error('Error update users:', error);
res.status(500).json({ error: 'เกิดข้อผิดพลาดในการอัปเดตสิทธิ์ผู้ใช้งาน' });
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Logging full req.user leaks token payload to server logs.

console.log("ข้อมูลใน Token ของฉัน:", req.user) writes the entire decoded user object (potentially including email/PII) to logs on every role-update call.

🔒 Proposed fix
-        console.log("ID จากหน้าบ้าน:", userId);
-        console.log("ข้อมูลใน Token ของฉัน:", req.user);
+        // debug logs removed
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const updateRoleUsers = async (req, res) => {
try {
const { userId, userRole } = req.body;
const myUserId = req.user.userId;
console.log("ID จากหน้าบ้าน:", userId);
console.log("ข้อมูลใน Token ของฉัน:", req.user);
if (String(userId) === String(myUserId)) {
return res.status(400).json({ error: "ไม่อนุญาตให้แก้ไขสิทธิ์ของตัวเอง" });
}
const users = await prisma.user.update({
where: {
userId: Number(userId)
}, data: {
role: userRole
}
});
res.status(200).json(users);
} catch (error) {
console.error('Error update users:', error);
res.status(500).json({ error: 'เกิดข้อผิดพลาดในการอัปเดตสิทธิ์ผู้ใช้งาน' });
}
};
export const updateRoleUsers = async (req, res) => {
try {
const { userId, userRole } = req.body;
const myUserId = req.user.userId;
// debug logs removed
if (String(userId) === String(myUserId)) {
return res.status(400).json({ error: "ไม่อนุญาตให้แก้ไขสิทธิ์ของตัวเอง" });
}
const users = await prisma.user.update({
where: {
userId: Number(userId)
}, data: {
role: userRole
}
});
res.status(200).json(users);
} catch (error) {
console.error('Error update users:', error);
res.status(500).json({ error: 'เกิดข้อผิดพลาดในการอัปเดตสิทธิ์ผู้ใช้งาน' });
}
};
🤖 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 `@server/controllers/managementControllers.js` around lines 449 - 471, The
updateRoleUsers handler is logging the full req.user token payload, which can
leak sensitive user data into server logs. Remove the console.log that prints
the entire req.user object and keep only minimal, non-sensitive debugging output
if needed, using updateRoleUsers and myUserId as the key places to adjust.
Ensure the role update flow still validates self-edits and continues to log only
safe identifiers such as userId when necessary.

Comment on lines +459 to +465
const users = await prisma.user.update({
where: {
userId: Number(userId)
}, data: {
role: userRole
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

userRole is written to the DB without validating it against allowed values.

Any string sent in req.body.userRole is persisted directly to role. An unexpected/typo'd value would silently break every role === 'admin'/role-based check across the app (e.g., routing/authorization logic in App.jsx), with no server-side guardrail.

🛡️ Proposed fix
+        const ALLOWED_ROLES = ['admin', 'user'];
+        if (!ALLOWED_ROLES.includes(userRole)) {
+            return res.status(400).json({ error: 'บทบาทไม่ถูกต้อง' });
+        }
         const users = await prisma.user.update({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const users = await prisma.user.update({
where: {
userId: Number(userId)
}, data: {
role: userRole
}
});
const ALLOWED_ROLES = ['admin', 'user'];
if (!ALLOWED_ROLES.includes(userRole)) {
return res.status(400).json({ error: 'บทบาทไม่ถูกต้อง' });
}
const users = await prisma.user.update({
where: {
userId: Number(userId)
}, data: {
role: userRole
}
});
🤖 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 `@server/controllers/managementControllers.js` around lines 459 - 465, The
userRole value is being written directly through prisma.user.update without any
server-side validation, so add an allowlist check before updating the user. In
the management controller that handles this update, validate req.body.userRole
against the permitted role values used by the app, reject invalid input with an
error response, and only pass the sanitized role into the update call. Keep the
fix localized around the userRole handling in this controller so role-based
checks elsewhere remain consistent.

Comment on lines +6 to +19
const MostCategories = await prisma.TicketCategory.findMany({
include: {
_count: {
select: {
tickets: true
}
}
},
orderBy: {
tickets: {
_count: "desc"
}
},
});

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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- Files of interest ---\n'
git ls-files | rg '(^|/)(schema\.prisma|.*prisma.*|server/controllers/statisticController\.js|prisma)' || true

printf '\n--- Search for TicketCategory usage ---\n'
rg -n "TicketCategory|ticketCategory|prisma\.[A-Za-z0-9_]+\.findMany" server prisma . || true

printf '\n--- If schema exists, show model names ---\n'
if git ls-files | rg -q 'schema\.prisma$'; then
  schema_path="$(git ls-files | rg 'schema\.prisma$' | head -n1)"
  echo "Schema: $schema_path"
  rg -n '^\s*model\s+[A-Za-z0-9_]+' "$schema_path" || true
fi

Repository: CNPJB/trouble-tracking-system-project

Length of output: 266


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Files of interest ---'
git ls-files | rg '(^|/)(schema\.prisma|.*prisma.*|server/controllers/statisticController\.js|prisma)' || true

printf '%s\n' ''
printf '%s\n' '--- Search for TicketCategory usage ---'
rg -n "TicketCategory|ticketCategory|prisma\.[A-Za-z0-9_]+\.findMany" server prisma . || true

printf '%s\n' ''
printf '%s\n' '--- Schema model names (if present) ---'
schema_path="$(git ls-files | rg 'schema\.prisma$' | head -n1 || true)"
if [ -n "${schema_path}" ]; then
  echo "Schema: ${schema_path}"
  rg -n '^\s*model\s+[A-Za-z0-9_]+' "${schema_path}" || true
fi

printf '%s\n' ''
printf '%s\n' '--- statisticController.js excerpt ---'
sed -n '1,120p' server/controllers/statisticController.js

Repository: CNPJB/trouble-tracking-system-project

Length of output: 10859


Use camelCase Prisma accessors here. prisma.TicketCategory and prisma.Ticket don’t exist on the generated client; this should be prisma.ticketCategory and prisma.ticket, or these endpoints will fail at runtime.

🤖 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 `@server/controllers/statisticController.js` around lines 6 - 19, The Prisma
model accessors in the statistics controller are using the wrong casing, which
will break at runtime because the generated client exposes camelCase names.
Update the queries in statisticController to use prisma.ticketCategory (and
prisma.ticket where applicable) instead of prisma.TicketCategory /
prisma.Ticket, keeping the same include and orderBy logic.

Comment on lines +36 to +50
const mostUpvotedTickets = await prisma.Ticket.findMany({
orderBy: {
upvotes: {
_count: "desc"
}
},
include: {
_count: {
select: { upvotes: true }
},
location: true,
},

take: 5
});

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files | rg '(^|/)(schema\.prisma|.*prisma.*|server/controllers/statisticController\.js)$' && printf '\n-- controller --\n' && sed -n '1,120p' server/controllers/statisticController.js && printf '\n-- prisma casing usage --\n' && rg -n 'prisma\.(Ticket|ticket)\b' . && printf '\n-- schema models --\n' && rg -n '^\s*model\s+\w+' prisma server -g 'schema.prisma'

Repository: CNPJB/trouble-tracking-system-project

Length of output: 6897


🏁 Script executed:

sed -n '100,140p' server/prisma/schema.prisma && printf '\n--\n' && sed -n '1,40p' server/prisma/schema.prisma

Repository: CNPJB/trouble-tracking-system-project

Length of output: 3773


Use the generated Prisma delegate names here prisma.Ticket should be prisma.ticket here, and prisma.TicketCategory should be prisma.ticketCategory; the current casing will fail at runtime.

🤖 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 `@server/controllers/statisticController.js` around lines 36 - 50, The Prisma
delegate names in statisticController are using the wrong casing and will fail
at runtime. Update the queries in the relevant controller method to use the
generated lowercase delegates on prisma, replacing prisma.Ticket with
prisma.ticket and any prisma.TicketCategory usage with prisma.ticketCategory,
while keeping the existing findMany and related include/orderBy logic unchanged.

@poner2547
poner2547 merged commit af8b47b into develop Jul 7, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant