Skip to content

Feature/issue management - #14

Merged
CNPJB merged 3 commits into
developfrom
feature/IssueManagement
Jul 7, 2026
Merged

Feature/issue management#14
CNPJB merged 3 commits into
developfrom
feature/IssueManagement

Conversation

@CNPJB

@CNPJB CNPJB commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Add 2 commit: Edit folder name/path and Add Feature.

Summary by CodeRabbit

  • New Features

    • Added admin issue management pages with ticket lists, urgent items, ticket details, merge/unmerge actions, and status updates.
    • Added ticket feedback, category/location/status filters, and improved infinite scrolling and counts in tracking views.
    • Added support for merged-ticket badges, sub-ticket counts, and richer ticket status handling.
  • Bug Fixes

    • Improved ticket timestamps, author/operator display, and search field behavior.
    • Prevented edits/submissions when no changes were made and tightened equipment availability checks.
    • Updated ticket ratings to support half-star feedback.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@CNPJB, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 759d61ab-982d-46a9-aecb-479db6e2677b

📥 Commits

Reviewing files that changed from the base of the PR and between 22e334e and 473ade9.

⛔ Files ignored due to path filters (1)
  • client/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • client/package.json
  • client/src/App.jsx
  • client/src/components/CardFinishProblem.jsx
  • client/src/components/CardPendingProblem.jsx
  • client/src/components/FilterProblem.jsx
  • client/src/components/componentsStyles/CardPendingProblem.css
  • client/src/components/componentsStyles/FilterProblem.css
  • client/src/components/componentsStyles/SearchBar.css
  • client/src/components/componentsStyles/SimilarTickets.css
  • client/src/hooks/useTickets.js
  • client/src/index.css
  • client/src/pages/AddIssue.jsx
  • client/src/pages/Dashboard.jsx
  • client/src/pages/DetailTicket.jsx
  • client/src/pages/adminPage/AssetManagement.jsx
  • client/src/pages/pageStyles/DetailTicket.css
  • client/src/pages/pageStyles/Tracking.css
  • client/src/utils/timeline.js
  • server/controllers/managementControllers.js
  • server/controllers/ticketControllers.js
  • server/routes/managementRoutes.js
  • server/routes/ticketRoutes.js
📝 Walkthrough

Walkthrough

This PR adds server-side ticket management APIs (merge/unmerge, urgent tickets, admin status update, feedback), new admin Issue Management and Merge Management UI, ticket feedback and bulk-cancel flows on Tracking, supporting hooks/services, a Prisma rating type migration, and a broad CSS import path reorganization with new design tokens.

Changes

Ticket Management Feature Set

Layer / File(s) Summary
Server ticket management controllers, routes, migration
server/controllers/ticketManagementControllers.js, server/routes/ticketManagementRoutes.js, server/routes/managementRoutes.js, server/controllers/managementControllers.js, server/src/index.js, server/prisma/schema.prisma, server/prisma/migrations/...
Adds mergeTickets/unmergeTickets/getTicketGroups/getUrgentTickets/updateTicketStatusAdmin handlers and routes, removes the old mergeTickets controller/route, and migrates Ticket.rating to Float.
Ticket read/query endpoints and feedback
server/controllers/getTicketControllers.js, server/controllers/ticketControllers.js, server/routes/ticketRoutes.js
Adds getAllTickets, getTicketSummary, getSimilarTickets, getTicketById, and submitFeedback controllers/routes; fixes async ticket ID generation.
Client ticketService wrappers and React Query hooks
client/src/services/ticketService.js, client/src/main.jsx, client/src/hooks/useTicketGroups.js, client/src/hooks/useUrgentTickets.js, client/src/hooks/useMasterData.js, client/src/hooks/useTickets.js, client/src/hooks/useTicketDetail.js, client/src/hooks/useInfiniteScroll.js, client/package.json, server/package.json
Adds API wrappers, React Query provider setup, and hooks for ticket groups, urgent tickets, mode-based ticket lists, and infinite scroll.
Admin Issue Management pages and filters
client/src/pages/adminPage/IssueManagement*, client/src/components/componentsAdmin/TicketActionPanel*, client/src/components/Ticket*Filter.jsx/.css, client/src/App.jsx, client/src/components/componentsAdmin/AdminSidebar.jsx, Adminsidebar.css
Introduces new admin pages, an action panel for ticket status updates, and category/location/status filter components with routing wiring.
AuditIssues merge/unmerge refactor
client/src/pages/adminPage/AuditIssues.jsx/.css, client/src/components/componentsAdmin/MergeManagementPanel.jsx/.css
Refactors AuditIssues to use ticket grouping and delegates merge/unmerge UI to a new MergeManagementPanel component.
Tracking feedback and bulk-cancel flow
client/src/pages/Tracking.jsx, client/src/components/FeedbackModal.jsx/.css, client/src/components/TrackingSidebar.*, client/src/components/CardPendingProblem.jsx, client/src/components/CardFinishProblem.jsx, client/src/utils/timeline.js, client/src/hooks/useEquipmentValidation.js
Adds feedback submission modal, bulk cancel-mode selection, sidebar counts, and infinite scroll wiring.
Issue form updates
client/src/pages/DetailTicket.jsx, client/src/pages/EditIssue.jsx, client/src/pages/AddIssue.jsx
Updates operator display, edit no-change guard, image upload limits, and refetch behavior after submit/upvote.

Component Style Reorganization

Layer / File(s) Summary
Stylesheet import path migration
client/src/components/*.jsx, client/src/pages/*.jsx
Updates CSS import paths to reference new componentsStyles/pageStyles directories.
Design tokens and misc CSS tweaks
client/src/index.css, client/src/components/componentsStyles/*, client/src/pages/pageStyles/*, client/src/components/Navbar.jsx, client/src/components/SearchBar.jsx, client/src/pages/adminPage/AssetManagement.jsx
Adds new status/typography CSS variables, responsive breakpoints, and small UI text/behavior tweaks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant AuditIssues
  participant MergeManagementPanel
  participant ticketService
  participant Server

  Admin->>AuditIssues: select tickets, confirm merge
  AuditIssues->>MergeManagementPanel: pass selection/state
  AuditIssues->>ticketService: mergeTickets(primary, duplicates)
  ticketService->>Server: PATCH /api/ticketManagement/mergeTickets
  Server-->>ticketService: merge result
  ticketService-->>AuditIssues: response
  AuditIssues->>AuditIssues: refetch tickets and groups
Loading
sequenceDiagram
  participant User
  participant Tracking
  participant FeedbackModal
  participant ticketService
  participant Server

  User->>Tracking: click Feedback on ticket
  Tracking->>FeedbackModal: open(ticketId)
  User->>FeedbackModal: submit rating/comment
  FeedbackModal->>Tracking: onSubmit(ticketId, payload)
  Tracking->>ticketService: submitFeedback(ticketId, payload)
  ticketService->>Server: POST /api/tickets/submitFeedback/:id
  Server-->>ticketService: success
  Tracking->>Tracking: updateTicketStatus(resolved), refetch, refresh sidebar counts
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too vague and generic to clearly describe the main change in the pull request. Use a specific title that names the primary feature, such as admin issue management or issue tracking updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/IssueManagement

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: 20

🧹 Nitpick comments (10)
client/src/index.css (2)

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

Naming: --text-size-base vs --text-size-normal is ambiguous.

Having both base (14px) and normal (16px) alongside small/large makes the scale's intent unclear at call sites. Consider a single consistent naming scheme (e.g., xs/sm/base/lg or a numeric scale).

🤖 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/index.css` around lines 118 - 120, The text-size CSS variables use
an inconsistent naming scheme, which makes the scale unclear at call sites.
Update the custom properties in the root token set to follow one consistent
pattern in the same section as --text-size-small, --text-size-base, and
--text-size-normal, and rename any related usages throughout the client styles
to match the chosen convention.

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

New --status-reject-* tokens reuse a low-contrast color pair.

--status-reject-bg: #e74c3c with `--status-reject-text: `#ffffff yields roughly 3.8:1 contrast, below the WCAG AA minimum of 4.5:1 for normal text. This mirrors the pre-existing --button-red-bg/white pairing elsewhere in the file, so it's a pre-existing pattern rather than a new mistake, but worth reconsidering for the new status label usage.

🤖 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/index.css` around lines 79 - 86, The new `--status-reject-*`
tokens in `index.css` use a red/white pair that does not meet WCAG AA contrast
for normal text. Update the `--status-reject-bg` and/or `--status-reject-text`
values to a higher-contrast combination while keeping the new status label
styling consistent, and verify the same token pair is used wherever the reject
status is rendered.
client/src/pages/pageStyles/AddIssue.css (1)

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

Hardcoded font-size breaks the new design-token pattern.

.form-section h2 now uses var(--text-size-large) at Line 27, but its 1536px breakpoint override at Line 158 falls back to a hardcoded 18px. Consider using a token (e.g., a smaller --text-size-* variable) here too for consistency.

♻️ Suggested fix
   .form-section h2 {
-    font-size: 18px;
+    font-size: var(--text-size-normal);
     margin-bottom: 15px;
   }
🤖 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/pageStyles/AddIssue.css` around lines 148 - 171, The
responsive override for .form-section h2 in AddIssue.css is still using a
hardcoded font size, which breaks the design-token approach used by the base
rule. Update the 1536px media query so the .form-section h2 font-size also uses
an appropriate text-size token instead of 18px, keeping it consistent with the
existing var(--text-size-large) usage and the rest of the AddIssue styles.
client/src/pages/pageStyles/Dashboard.css (1)

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

Merge duplicate .ticket-pending-list selector in the same media block.

The selector is declared twice back-to-back; consolidating avoids redundant rule blocks.

♻️ Suggested fix
 `@media` (max-width: 1536px) {
     .ticket-pending-list {
         grid-template-columns: repeat(3, 1fr);
-    }
-    .ticket-pending-list {
         gap: 15px;
         padding: 10px 80px;
     }
 }
🤖 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/pageStyles/Dashboard.css` around lines 93 - 101, The `@media`
block contains two back-to-back .ticket-pending-list rules, which should be
merged into a single selector. Consolidate the properties in the Dashboard.css
media query so .ticket-pending-list defines both grid-template-columns and the
gap/padding together, removing the redundant duplicate block.
client/src/pages/adminPage/IssueManagement.jsx (1)

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

Import references a mistyped filename (TIcketStatusFilter.jsx).

Matches the actual (typo'd) filename, so it works, but the typo should eventually be fixed at the source across all importers.

🤖 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/IssueManagement.jsx` at line 23, The import in
IssueManagement.jsx is pointing to a mistyped module name, so update the
TicketStatusFilter import to use the correctly spelled filename and keep the
symbol TicketStatusFilter unchanged. Check all other importers of
TicketStatusFilter as well and make them reference the corrected component path
so the typo is fixed consistently at the source.
client/src/components/componentsAdmin/MergeManagementPanel.jsx (1)

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

Duplicated ticket-item markup across four render sites.

The badge/title/meta JSX for main-ticket, sub-ticket (merge tab), and group main/sub-ticket (manage tab) is repeated near-verbatim four times. Extracting a small TicketListItem (or similar) helper component parameterized by variant (main/sub) and action button would reduce this duplication and ease future styling/behavior changes.

🤖 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/MergeManagementPanel.jsx` around lines
66 - 207, The ticket-item JSX is duplicated across the merge and manage render
branches, including the badge/title/meta blocks and action buttons. Extract the
repeated markup into a reusable helper component such as TicketListItem in
MergeManagementPanel and parameterize it for main vs sub variants, ticket data,
and optional actions like remove/unlink/disband so the four render sites share
one implementation.
client/src/components/componentsStyles/TicketCategoryFilter.css (1)

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

Consolidate triplicated filter stylesheet.

This file, TicketLocationFilter.css, and TicketStatusFilter.css are essentially copy-pasted (identical select/wrapper/focus/disabled rules and SVG background-image), differing only in class prefix and width values. Consider a shared base class (e.g., .ticket-filter-wrapper / .ticket-filter-select) applied alongside a per-type modifier class for width overrides, to avoid three-way maintenance drift going forward.

♻️ Example consolidation approach
.ticket-filter-wrapper { position: relative; display: flex; align-items: center; }
.ticket-filter-select { width: 100%; padding: 12px 35px 12px 40px; border: 2px solid `#e0e0e0`; border-radius: 25px; background-color: `#ffffff`; font-size: 15px; color: `#334155`; cursor: pointer; appearance: none; background-image: url("..."); background-repeat: no-repeat; background-position: right 16px top 50%; background-size: 12px auto; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }
.ticket-filter-select:focus { outline: none; border-color: var(--tbs-green); box-shadow: 0 4px 10px rgba(0, 123, 255, 0.1); }
.ticket-filter-select:disabled { background-color: `#f1f5f9`; color: `#94a3b8`; cursor: not-allowed; opacity: 0.8; }

.category-filter-wrapper { width: 220px; }
.location-filter-wrapper,
.status-filter-wrapper { width: 180px; }
🤖 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/componentsStyles/TicketCategoryFilter.css` around lines
1 - 57, The filter CSS rules are duplicated across the ticket category,
location, and status stylesheets, so consolidate the shared
wrapper/select/focus/disabled/SVG background styles into one base set (for
example, a shared `.ticket-filter-wrapper` and `.ticket-filter-select`). Keep
only the type-specific width overrides in `TicketCategoryFilter.css` and the
other filter styles, and update the corresponding components to apply the shared
base class plus any modifier class needed for the width differences.
client/src/pages/adminPage/AuditIssues.css (1)

6-33: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Flex-child widths + margin sum beyond container width.

.audit-issues-card-list (width: 60% + margin-right: 2rem) and .audit-right-panel (width: 40%) sit in the same flex row (.audit-issues-content) with no gap defined, so their combined footprint exceeds 100% of the container. This currently relies on implicit flex-shrink rather than explicit spacing, and could cause squeezing/overflow at some viewport widths. Consider using gap on .audit-issues-content instead of margin-right on the card list.

♻️ Proposed fix
 .audit-issues-content {
   display: flex;
+  gap: 2rem;
 }

 .audit-issues-card-list {
   display: grid;
   grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
   border-radius: 2rem;
   gap: 20px;
-  margin-right: 2rem;
   width: 60%;
   align-content: start;
 }
🤖 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/AuditIssues.css` around lines 6 - 33, The flex
layout in .audit-issues-content is overcommitted because .audit-issues-card-list
uses width: 60% plus margin-right while .audit-right-panel uses width: 40%,
which can cause squeezing or overflow. Move the spacing responsibility to
.audit-issues-content by adding a gap and remove the right margin from
.audit-issues-card-list; keep the two child widths in .audit-issues-card-list
and .audit-right-panel aligned so the row fits cleanly.
server/controllers/ticketManagementControllers.js (1)

258-296: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Full ticket payload fetched for all pending tickets just to compute a 5-item ranking.

getUrgentTickets loads location, floor, room, images for every pending ticket to compute an in-memory urgency score, then discards all but the top 5. As pending-ticket volume grows this becomes an unnecessary I/O/memory cost on every call.

♻️ Suggested two-step approach
-        const pendingTickets = await prisma.ticket.findMany({
-            where: { ticketStatus: 'pending' },
-            include: {
-                location: true,
-                floor: true,
-                room: true,
-                images: true,
-                _count: { select: { subTickets: true } },
-                upvotes: { select: { upvoteId: true } }
-            }
-        });
+        // 1) Lightweight pass to compute scores
+        const scoreCandidates = await prisma.ticket.findMany({
+            where: { ticketStatus: 'pending' },
+            select: {
+                ticketId: true,
+                _count: { select: { subTickets: true, upvotes: true } }
+            }
+        });
+        const top5Ids = scoreCandidates
+            .map(t => ({ ticketId: t.ticketId, score: (t._count.subTickets * 5) + t._count.upvotes }))
+            .filter(t => t.score > 0)
+            .sort((a, b) => b.score - a.score)
+            .slice(0, 5);
+        // 2) Fetch full data only for the winners
+        const pendingTickets = await prisma.ticket.findMany({
+            where: { ticketId: { in: top5Ids.map(t => t.ticketId) } },
+            include: { location: true, floor: true, room: true, images: 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 `@server/controllers/ticketManagementControllers.js` around lines 258 - 296,
The getUrgentTickets controller is over-fetching full ticket payloads for every
pending ticket just to rank and return the top 5. Refactor this logic to use a
two-step approach in ticketManagementControllers: first fetch only the minimal
fields needed to compute urgencyScore (for example ticketId, subTickets count,
and upvote count), sort and select the top 5, then load the expanded relations
(location, floor, room, images) only for those selected tickets before sending
the response.
server/controllers/ticketControllers.js (1)

21-21: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant initial generateTicketId() call.

customTicketId generated at line 21 is only ever used to build dataToCreate.ticketId, but the retry loop (line 89-90) always regenerates and overwrites dataToCreate.ticketId before the first create attempt, making the outer generation a wasted async call.

♻️ Proposed fix
-        const files = req.files; // รับไฟล์จาก multer
-        const customTicketId = await generateTicketId();
+        const files = req.files; // รับไฟล์จาก multer

Then initialize dataToCreate.ticketId inside the retry loop only (already done at line 90), or remove the redundant field from the initial dataToCreate object until the loop assigns it.

Also applies to: 89-90

🤖 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/ticketControllers.js` at line 21, Remove the redundant
outer generateTicketId() call in ticketControllers.js: the initial
customTicketId is immediately overwritten by the retry logic before the first
create attempt. Update the dataToCreate initialization so ticketId is assigned
only inside the retry loop in the ticket creation flow, using the existing
generateTicketId() assignment there and dropping the unused initial field.
🤖 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/componentsAdmin/MergeManagementPanel.css`:
- Around line 443-452: The keyframe identifier in the `@keyframes slideDown`
block should use kebab-case to match the codebase/stylelint convention. Rename
the animation definition to a kebab-case name and update any matching
`animation` references in `MergeManagementPanel` styles so they still point to
the renamed keyframes.

In `@client/src/components/componentsStyles/FeedbackModal.css`:
- Around line 9-15: The FeedbackModal styles have a blank line before the
animation declaration, which violates the stylelint formatting rule. Remove the
empty line in the modal/container CSS block so the animation property sits
directly with the other declarations, and apply the same cleanup to the other
flagged block in FeedbackModal.css. Keep the change limited to the relevant
selector blocks in the stylesheet.
- Around line 189-197: The keyframe names in FeedbackModal.css use camelCase,
which violates the kebab-case stylelint rule. Rename the `@keyframes` definitions
from fadeIn and slideUp to kebab-case names, and update the matching animation
references in the modal styles to use the new names so the component still
animates correctly.

In `@client/src/components/FeedbackModal.jsx`:
- Around line 93-104: The star rating in FeedbackModal is mouse-only, so make
the interactive star wrappers keyboard-accessible and screen-reader friendly.
Update the star wrapper render in the star list mapping to use semantic
button-like behavior or equivalent by adding an appropriate role, tab focus, and
keyboard handlers alongside the existing mouse handlers. Ensure the rating can
be changed with Enter/Space and that the current selection is announced so
handlePreSubmit can be satisfied without a mouse.

In `@client/src/components/TIcketStatusFilter.jsx`:
- Line 4: The import path in TicketStatusFilter.jsx is pointing to the wrong
stylesheet directory name, causing module resolution to fail. Update the CSS
import to use the actual componentsStyles folder, and apply the same correction
in TicketCategoryFilter.jsx and TicketLocationFilter.jsx where the same typo
appears. Keep the import paths consistent with the existing componentStyles
naming used by the project.

In `@client/src/hooks/useInfiniteScroll.js`:
- Around line 11-29: The early return in useInfiniteScroll’s lastElementRef
skips disconnecting the existing IntersectionObserver when node is cleared
during loading, which can leave a stale observer attached. Move the
observerRef.current.disconnect() cleanup to happen before the isLoading /
isFetchingNextPage return, and keep the rest of lastElementRef’s observe/setup
logic unchanged so null callback refs always clean up properly.

In `@client/src/hooks/useTicketDetail.js`:
- Around line 22-26: The useTicketDetail hook currently handles only the success
path after ticketService.getTicketById(ticketId), so when response.success is
false the UI never gets an error state. Update the logic in useTicketDetail to
add an explicit else branch alongside setTicket(response.data) that sets a
meaningful error message (and leaves ticket unset) when the API returns success:
false, so the hook can distinguish a failed response from a loading or empty
state.

In `@client/src/hooks/useTickets.js`:
- Around line 108-114: The refetch function in useTickets.js is using a fixed
timeout to resolve, so callers may continue before fetchTickets has actually
finished updating state. Replace the setTimeout-based resolution in refetch with
a completion-driven approach by using refetchResolversRef.current and resolving
any pending promises from the finally block in fetchTickets after loading ends.
Keep the change localized around refetch, fetchTickets, and the existing
refetchResolversRef logic so awaiting refetch() only completes once the refresh
is truly done.

In `@client/src/pages/adminPage/AuditIssues.css`:
- Around line 14-16: The block comment in AuditIssues.css is flagged by
stylelint because the comment delimiters are missing the expected internal
whitespace. Update the existing section comment so it follows the standard
block-comment spacing used by the stylesheet, keeping the same meaning while
fixing the formatting in the AuditIssues.css comment block.

In `@client/src/pages/adminPage/AuditIssues.jsx`:
- Around line 248-262: `MergeManagementPanel` is always receiving
`isLoading={false}`, so its reset/confirm disabled state never reflects the real
request status. Update `AuditIssues` to pass the actual loading flag from
`useLoadingState` (the `loading.isLoading` value or equivalent) into
`MergeManagementPanel` instead of a hardcoded false, so `handleResetSelection`
and `handleConfirmMerge` stay guarded during in-flight merge/unmerge actions.
- Around line 93-148: In submitMerge and submitUnmerge in AuditIssues.jsx,
handle the case where ticketService.mergeTickets and
ticketService.unmergeTickets resolve with success false instead of throwing;
currently only the success path clears the loading state and closes the modal.
Add an explicit else path after checking result.success that sets an error
message, stops the loading/progress state, and closes the confirm modal (or
otherwise resets it) so the UI does not մն stay stuck on "กำลังประมวลผล...". Use
the existing submitMerge, submitUnmerge, setError, setConfirmMergeModal,
setConfirmUnmergeModal, and startLoading flow as the entry points.

In `@client/src/pages/adminPage/IssueManagement.jsx`:
- Line 104: The loading state in IssueManagement is unreachable because the
`isLoading && normalTickets.length === 0` skeleton is nested inside the
`normalTickets.length === 0 ? ... : ...` branch, so it never renders when
needed. Restore the early-loading guard in `IssueManagement` before the
empty-state check, and remove the dead loading skeleton logic from the
`normalTickets` conditional so first render and filter changes show the loading
indicator instead of the false “no issues” message.

In `@client/src/pages/adminPage/IssueManagementDetail.jsx`:
- Around line 133-135: The conditional in IssueManagementDetail’s ticket image
fallback has an operator precedence bug, so the “no images” message is skipped
when ticket.images is missing or undefined. Update the JSX condition around the
ticket.images check to explicitly group the null/empty cases before rendering
the frame-no-images block, using the same ticket.images reference in
IssueManagementDetail so the fallback displays whenever there are no images.
- Around line 36-53: In submitUpdateStatus, handle the non-success response path
from ticketService.updateTicketStatusAdmin the same way as failures so the UI
does not stay loading forever. If result.success is falsy, set an error message,
close confirmModal, and ensure loading is cleared after the request finishes;
keep the existing success path and catch block behavior in
IssueManagementDetail.jsx. Also make sure any loading state started by
startLoading is always paired with a completion/reset path regardless of
outcome.

In `@client/src/pages/EditIssue.jsx`:
- Around line 344-355: The disabled-state guard in EditIssue.jsx is missing
formData.equipmentCode, so changes to equipment code are treated as “no changes”
and keep the Submit button disabled. Update the no-change comparison in the
button’s disabled condition to also compare formData.equipmentCode against
ticket.equipment?.equipmentCode, alongside the existing
categoryId/title/locationId/floorId/roomId/description/image checks, so
equipment-category edits are recognized as changes.

In `@client/src/pages/Tracking.jsx`:
- Around line 194-218: `handleConfirmCancelSubmit` currently uses `Promise.all`,
so one rejected `ticketService.cancelTicket` call prevents successful
cancellations in `selectedToCancel` from being removed locally. Update the bulk
cancel flow to use `Promise.allSettled` (or equivalent per-ticket handling) so
you can remove only the tickets that were actually canceled, then keep the
existing `removeTicket`, `refetch`, and `fetchSidebarCounts` steps in sync with
the partial success state. Make sure `setError` still reports failures for
rejected items while preserving the UI updates for fulfilled cancellations.

In `@client/src/services/ticketService.js`:
- Around line 144-150: The axios.patch call in ticketService’s
updateTicketStatusAdmin request is manually setting the multipart Content-Type
header for FormData, which can prevent the boundary from being added correctly.
Remove the explicit 'Content-Type': 'multipart/form-data' header from this
upload path and let axios/browser infer it automatically while keeping the
withCredentials option and existing request structure intact.

In `@server/controllers/getTicketControllers.js`:
- Around line 103-139: The getTicketControllers response select object has a
duplicate equipment key, so the earlier equipmentName selection is being
overwritten and never returned. Update the ticket query in the controller to
merge both equipmentName and equipmentCode into a single equipment select entry,
keeping the existing nested select shape intact so the response includes all
intended equipment fields.

In `@server/controllers/ticketManagementControllers.js`:
- Around line 336-351: The Cloudinary upload flow in the ticket status update
path can leak successful uploads when one file fails because `Promise.all`
rejects before `cloudinaryResults.forEach` can record rollback IDs. Update the
`uploadToCloudinary` handling in this block to use a failure-tolerant approach
such as `Promise.allSettled` (or equivalent per-file try/catch) so every
successful upload is captured in `uploadedImagesForRollback` and later cleanup
can remove any partial uploads.

In
`@server/prisma/migrations/20260606071851_change_rating_to_float/migration.sql`:
- Around line 19-38: The migration in the tickets/floors/rooms/equipments
foreign key section is too blocking for production because the rating type
change and immediate FK creation can take exclusive locks. Update the migration
flow to avoid table-wide blocking by splitting each foreign key add into a NOT
VALID creation followed by a separate VALIDATE CONSTRAINT step, and keep the
tickets.rating type change in mind when ordering operations in this migration
file.

---

Nitpick comments:
In `@client/src/components/componentsAdmin/MergeManagementPanel.jsx`:
- Around line 66-207: The ticket-item JSX is duplicated across the merge and
manage render branches, including the badge/title/meta blocks and action
buttons. Extract the repeated markup into a reusable helper component such as
TicketListItem in MergeManagementPanel and parameterize it for main vs sub
variants, ticket data, and optional actions like remove/unlink/disband so the
four render sites share one implementation.

In `@client/src/components/componentsStyles/TicketCategoryFilter.css`:
- Around line 1-57: The filter CSS rules are duplicated across the ticket
category, location, and status stylesheets, so consolidate the shared
wrapper/select/focus/disabled/SVG background styles into one base set (for
example, a shared `.ticket-filter-wrapper` and `.ticket-filter-select`). Keep
only the type-specific width overrides in `TicketCategoryFilter.css` and the
other filter styles, and update the corresponding components to apply the shared
base class plus any modifier class needed for the width differences.

In `@client/src/index.css`:
- Around line 118-120: The text-size CSS variables use an inconsistent naming
scheme, which makes the scale unclear at call sites. Update the custom
properties in the root token set to follow one consistent pattern in the same
section as --text-size-small, --text-size-base, and --text-size-normal, and
rename any related usages throughout the client styles to match the chosen
convention.
- Around line 79-86: The new `--status-reject-*` tokens in `index.css` use a
red/white pair that does not meet WCAG AA contrast for normal text. Update the
`--status-reject-bg` and/or `--status-reject-text` values to a higher-contrast
combination while keeping the new status label styling consistent, and verify
the same token pair is used wherever the reject status is rendered.

In `@client/src/pages/adminPage/AuditIssues.css`:
- Around line 6-33: The flex layout in .audit-issues-content is overcommitted
because .audit-issues-card-list uses width: 60% plus margin-right while
.audit-right-panel uses width: 40%, which can cause squeezing or overflow. Move
the spacing responsibility to .audit-issues-content by adding a gap and remove
the right margin from .audit-issues-card-list; keep the two child widths in
.audit-issues-card-list and .audit-right-panel aligned so the row fits cleanly.

In `@client/src/pages/adminPage/IssueManagement.jsx`:
- Line 23: The import in IssueManagement.jsx is pointing to a mistyped module
name, so update the TicketStatusFilter import to use the correctly spelled
filename and keep the symbol TicketStatusFilter unchanged. Check all other
importers of TicketStatusFilter as well and make them reference the corrected
component path so the typo is fixed consistently at the source.

In `@client/src/pages/pageStyles/AddIssue.css`:
- Around line 148-171: The responsive override for .form-section h2 in
AddIssue.css is still using a hardcoded font size, which breaks the design-token
approach used by the base rule. Update the 1536px media query so the
.form-section h2 font-size also uses an appropriate text-size token instead of
18px, keeping it consistent with the existing var(--text-size-large) usage and
the rest of the AddIssue styles.

In `@client/src/pages/pageStyles/Dashboard.css`:
- Around line 93-101: The `@media` block contains two back-to-back
.ticket-pending-list rules, which should be merged into a single selector.
Consolidate the properties in the Dashboard.css media query so
.ticket-pending-list defines both grid-template-columns and the gap/padding
together, removing the redundant duplicate block.

In `@server/controllers/ticketControllers.js`:
- Line 21: Remove the redundant outer generateTicketId() call in
ticketControllers.js: the initial customTicketId is immediately overwritten by
the retry logic before the first create attempt. Update the dataToCreate
initialization so ticketId is assigned only inside the retry loop in the ticket
creation flow, using the existing generateTicketId() assignment there and
dropping the unused initial field.

In `@server/controllers/ticketManagementControllers.js`:
- Around line 258-296: The getUrgentTickets controller is over-fetching full
ticket payloads for every pending ticket just to rank and return the top 5.
Refactor this logic to use a two-step approach in ticketManagementControllers:
first fetch only the minimal fields needed to compute urgencyScore (for example
ticketId, subTickets count, and upvote count), sort and select the top 5, then
load the expanded relations (location, floor, room, images) only for those
selected tickets before sending the response.
🪄 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: 49ef2d50-41cb-425c-9007-c980b8e9d279

📥 Commits

Reviewing files that changed from the base of the PR and between 34297db and 22e334e.

⛔ Files ignored due to path filters (2)
  • client/package-lock.json is excluded by !**/package-lock.json
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (81)
  • 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/ConfirmButton.jsx
  • client/src/components/DateRangeFilter.jsx
  • client/src/components/FeedbackModal.jsx
  • client/src/components/FilterProblem.jsx
  • client/src/components/ImageUploader.jsx
  • client/src/components/LoadingSpinner.jsx
  • client/src/components/Navbar.jsx
  • client/src/components/SearchBar.jsx
  • client/src/components/SimilarTickets.jsx
  • client/src/components/StarRating.jsx
  • client/src/components/TIcketStatusFilter.jsx
  • client/src/components/TicketCategoryFilter.jsx
  • client/src/components/TicketLocationFilter.jsx
  • client/src/components/TrackingSidebar.css
  • client/src/components/TrackingSidebar.jsx
  • client/src/components/componentsAdmin/AdminSidebar.jsx
  • client/src/components/componentsAdmin/Adminsidebar.css
  • client/src/components/componentsAdmin/MergeManagementPanel.css
  • client/src/components/componentsAdmin/MergeManagementPanel.jsx
  • client/src/components/componentsAdmin/TicketActionPanel.css
  • client/src/components/componentsAdmin/TicketActionPanel.jsx
  • client/src/components/componentsStyles/CardPendingProblem.css
  • client/src/components/componentsStyles/ConfirmButton.css
  • client/src/components/componentsStyles/DateRangeFilter.css
  • client/src/components/componentsStyles/FeedbackModal.css
  • client/src/components/componentsStyles/FilterProblem.css
  • client/src/components/componentsStyles/ImageUploader.css
  • client/src/components/componentsStyles/LoadingSpinner.css
  • client/src/components/componentsStyles/Navbar.css
  • client/src/components/componentsStyles/SearchBar.css
  • client/src/components/componentsStyles/SimilarTickets.css
  • client/src/components/componentsStyles/TicketCategoryFilter.css
  • client/src/components/componentsStyles/TicketLocationFilter.css
  • client/src/components/componentsStyles/TicketStatusFilter.css
  • client/src/components/componentsStyles/TrackingSidebar.css
  • client/src/hooks/useEquipmentValidation.js
  • client/src/hooks/useInfiniteScroll.js
  • client/src/hooks/useMasterData.js
  • client/src/hooks/useTicketDetail.js
  • client/src/hooks/useTicketGroups.js
  • client/src/hooks/useTickets.js
  • client/src/hooks/useUrgentTickets.js
  • client/src/index.css
  • client/src/main.jsx
  • client/src/pages/AddIssue.jsx
  • client/src/pages/Dashboard.jsx
  • client/src/pages/DetailTicket.jsx
  • client/src/pages/EditIssue.jsx
  • client/src/pages/Login.jsx
  • client/src/pages/Tracking.jsx
  • client/src/pages/adminPage/AssetManagement.jsx
  • client/src/pages/adminPage/AuditIssues.css
  • client/src/pages/adminPage/AuditIssues.jsx
  • client/src/pages/adminPage/IssueManagement.css
  • client/src/pages/adminPage/IssueManagement.jsx
  • client/src/pages/adminPage/IssueManagementDetail.css
  • client/src/pages/adminPage/IssueManagementDetail.jsx
  • client/src/pages/pageStyles/AddIssue.css
  • client/src/pages/pageStyles/Dashboard.css
  • client/src/pages/pageStyles/DetailTicket.css
  • client/src/pages/pageStyles/EditIssue.css
  • client/src/pages/pageStyles/Login.css
  • client/src/pages/pageStyles/Tracking.css
  • client/src/services/ticketService.js
  • client/src/utils/timeline.js
  • server/controllers/getTicketControllers.js
  • server/controllers/managementControllers.js
  • server/controllers/ticketControllers.js
  • server/controllers/ticketManagementControllers.js
  • server/package.json
  • server/prisma/migrations/20260606071851_change_rating_to_float/migration.sql
  • server/prisma/schema.prisma
  • server/routes/managementRoutes.js
  • server/routes/ticketManagementRoutes.js
  • server/routes/ticketRoutes.js
  • server/src/index.js
💤 Files with no reviewable changes (3)
  • client/src/components/TrackingSidebar.css
  • client/src/components/CardPendingProblem.css
  • server/controllers/managementControllers.js

Comment on lines +443 to +452
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keyframe name not kebab-case.

Stylelint flags slideDown; rename to kebab-case for consistency with the rest of the codebase's naming convention.

🔧 Proposed fix
-@keyframes slideDown {
+@keyframes slide-down {
     from {
         opacity: 0;
         transform: translateY(-10px);
     }
     to {
         opacity: 1;
         transform: translateY(0);
     }
 }

And update the reference:

-    animation: slideDown 0.3s ease-out forwards;
+    animation: slide-down 0.3s ease-out forwards;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 443-443: Expected keyframe name "slideDown" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 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/MergeManagementPanel.css` around lines
443 - 452, The keyframe identifier in the `@keyframes slideDown` block should
use kebab-case to match the codebase/stylelint convention. Rename the animation
definition to a kebab-case name and update any matching `animation` references
in `MergeManagementPanel` styles so they still point to the renamed keyframes.

Source: Linters/SAST tools

Comment on lines +9 to +15
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;

animation: fadeIn 0.2s ease-in-out;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint: remove blank line before animation declaration.

🛠️ Proposed fix
     display: flex;
     align-items: center;
     justify-content: center;
     z-index: 1000;
-    
     animation: fadeIn 0.2s ease-in-out;
 }
 
 /* กล่องเนื้อหา Modal */
 .feedback-modal-content {
     background-color: `#ffffff`;
     width: 100%;
     max-width: 450px;
     border-radius: 20px;
     padding: 30px;
     position: relative;
     box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
-    
     animation: slideUp 0.3s ease-out;
 }

Also applies to: 25-28

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 14-14: Expected no empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 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/componentsStyles/FeedbackModal.css` around lines 9 -
15, The FeedbackModal styles have a blank line before the animation declaration,
which violates the stylelint formatting rule. Remove the empty line in the
modal/container CSS block so the animation property sits directly with the other
declarations, and apply the same cleanup to the other flagged block in
FeedbackModal.css. Keep the change limited to the relevant selector blocks in
the stylesheet.

Source: Linters/SAST tools

Comment on lines +189 to +197
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint: keyframe names must be kebab-case.

🛠️ Proposed fix
-@keyframes fadeIn {
+@keyframes fade-in {
     from { opacity: 0; }
     to { opacity: 1; }
 }
 
-@keyframes slideUp {
+@keyframes slide-up {
     from { opacity: 0; transform: translateY(20px); }
     to { opacity: 1; transform: translateY(0); }
 }

And update the two animation: references at lines 14 and 27 accordingly (fadeInfade-in, slideUpslide-up).

📝 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
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
`@keyframes` fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
`@keyframes` slide-up {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 189-189: Expected keyframe name "fadeIn" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)


[error] 194-194: Expected keyframe name "slideUp" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 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/componentsStyles/FeedbackModal.css` around lines 189 -
197, The keyframe names in FeedbackModal.css use camelCase, which violates the
kebab-case stylelint rule. Rename the `@keyframes` definitions from fadeIn and
slideUp to kebab-case names, and update the matching animation references in the
modal styles to use the new names so the component still animates correctly.

Source: Linters/SAST tools

Comment on lines +93 to +104
return (
<div
key={index}
className="star-wrapper"
onMouseMove={(e) => handleMouseMove(e, index)}
onMouseLeave={handleMouseLeave}
onClick={(e) => handleStarClick(e, index)}
>
<StarIcon color={color} size={32} className="star-icon-svg" />
</div>
);
});

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

Star rating is not keyboard-accessible.

The rating control is a required field (handlePreSubmit blocks submission below 0.5 stars) but each star is a plain <div> reachable only via mouse (onMouseMove/onClick), with no role, tabIndex, or onKeyDown. Keyboard-only and screen-reader users cannot complete this form.

🛠️ Proposed fix
             return (
                 <div 
                     key={index}
                     className="star-wrapper"
+                    role="button"
+                    tabIndex={0}
+                    aria-label={`ให้คะแนน ${index} ดาว`}
                     onMouseMove={(e) => handleMouseMove(e, index)}
                     onMouseLeave={handleMouseLeave}
                     onClick={(e) => handleStarClick(e, index)}
+                    onKeyDown={(e) => {
+                        if (e.key === 'Enter' || e.key === ' ') {
+                            e.preventDefault();
+                            setRating(index);
+                            setErrorMsg('');
+                        }
+                    }}
                 >
                     <StarIcon color={color} size={32} className="star-icon-svg" />
                 </div>
             );
📝 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
return (
<div
key={index}
className="star-wrapper"
onMouseMove={(e) => handleMouseMove(e, index)}
onMouseLeave={handleMouseLeave}
onClick={(e) => handleStarClick(e, index)}
>
<StarIcon color={color} size={32} className="star-icon-svg" />
</div>
);
});
return (
<div
key={index}
className="star-wrapper"
role="button"
tabIndex={0}
aria-label={`ให้คะแนน ${index} ดาว`}
onMouseMove={(e) => handleMouseMove(e, index)}
onMouseLeave={handleMouseLeave}
onClick={(e) => handleStarClick(e, index)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setRating(index);
setErrorMsg('');
}
}}
>
<StarIcon color={color} size={32} className="star-icon-svg" />
</div>
);
});
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 100-100: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(list-component-needs-key)

🤖 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/FeedbackModal.jsx` around lines 93 - 104, The star
rating in FeedbackModal is mouse-only, so make the interactive star wrappers
keyboard-accessible and screen-reader friendly. Update the star wrapper render
in the star list mapping to use semantic button-like behavior or equivalent by
adding an appropriate role, tab focus, and keyboard handlers alongside the
existing mouse handlers. Ensure the rating can be changed with Enter/Space and
that the current selection is announced so handlePreSubmit can be satisfied
without a mouse.

import React from 'react';
import { useMasterData } from '../hooks/useMasterData.js';
import { FaFilter } from 'react-icons/fa';
import '../components/componentStyles/TicketStatusFilter.css';

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

Broken import path: componentStyles vs actual componentsStyles directory.

This import references ../components/componentStyles/TicketStatusFilter.css, but per the file layout the stylesheet actually lives under componentsStyles (with an "s" after "component"). This is a module-resolution failure at build time. The same typo is repeated in TicketCategoryFilter.jsx and TicketLocationFilter.jsx.

🐛 Proposed fix
-import '../components/componentStyles/TicketStatusFilter.css';
+import './componentsStyles/TicketStatusFilter.css';
📝 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
import '../components/componentStyles/TicketStatusFilter.css';
import './componentsStyles/TicketStatusFilter.css';
🤖 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/TIcketStatusFilter.jsx` at line 4, The import path in
TicketStatusFilter.jsx is pointing to the wrong stylesheet directory name,
causing module resolution to fail. Update the CSS import to use the actual
componentsStyles folder, and apply the same correction in
TicketCategoryFilter.jsx and TicketLocationFilter.jsx where the same typo
appears. Keep the import paths consistent with the existing componentStyles
naming used by the project.

Comment on lines +194 to +218
const handleConfirmCancelSubmit = async () => {
startLoading();
try {
const cancelPromises = selectedToCancel.map(id => ticketService.cancelTicket(id));
await Promise.all(cancelPromises);

selectedToCancel.forEach(ticketId => {
removeTicket(ticketId);
});

setSuccess(`ยกเลิกรายการแจ้งปัญหาจำนวน ${selectedToCancel.length} รายการสำเร็จ`);

setIsCancelMode(false);
setSelectedToCancel([]);
setConfirmCancelSubmit({ isOpen: false });
// ✅ รอให้ Backend sync ข้อมูลเสร็จ
await refetch();
fetchSidebarCounts();

} catch (error) {
console.error("Error bulk canceling tickets:", error);
setError(error.response?.data?.message || "เกิดข้อผิดพลาดในการยกเลิกรายการ");
setConfirmCancelSubmit({ 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.

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

Bulk cancel uses Promise.all; a single failure discards all local state updates even for tickets that succeeded server-side.

If one cancelTicket call rejects, Promise.all rejects immediately, so the catch block runs and none of selectedToCancel are removed locally — even though other tickets in the batch may have already been canceled on the backend. This leaves the UI showing stale (already-canceled) tickets as still active until a manual refresh.

🛠️ Proposed fix using `Promise.allSettled`
     const handleConfirmCancelSubmit = async () => {
         startLoading();
         try {
-            const cancelPromises = selectedToCancel.map(id => ticketService.cancelTicket(id));
-            await Promise.all(cancelPromises);
-
-            selectedToCancel.forEach(ticketId => {
-                removeTicket(ticketId);
-            });
-
-            setSuccess(`ยกเลิกรายการแจ้งปัญหาจำนวน ${selectedToCancel.length} รายการสำเร็จ`);
+            const results = await Promise.allSettled(
+                selectedToCancel.map(id => ticketService.cancelTicket(id))
+            );
+
+            const succeeded = selectedToCancel.filter((_, i) => results[i].status === 'fulfilled');
+            succeeded.forEach(ticketId => removeTicket(ticketId));
+
+            const failedCount = results.length - succeeded.length;
+            if (failedCount > 0) {
+                setError(`ยกเลิกสำเร็จ ${succeeded.length} รายการ, ล้มเหลว ${failedCount} รายการ`);
+            } else {
+                setSuccess(`ยกเลิกรายการแจ้งปัญหาจำนวน ${selectedToCancel.length} รายการสำเร็จ`);
+            }
 
             setIsCancelMode(false);
             setSelectedToCancel([]);
             setConfirmCancelSubmit({ isOpen: false });
             // ✅ รอให้ Backend sync ข้อมูลเสร็จ
             await refetch();
             fetchSidebarCounts();
-
         } catch (error) {
             console.error("Error bulk canceling tickets:", error);
             setError(error.response?.data?.message || "เกิดข้อผิดพลาดในการยกเลิกรายการ");
             setConfirmCancelSubmit({ isOpen: false });
         }
     };
📝 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 handleConfirmCancelSubmit = async () => {
startLoading();
try {
const cancelPromises = selectedToCancel.map(id => ticketService.cancelTicket(id));
await Promise.all(cancelPromises);
selectedToCancel.forEach(ticketId => {
removeTicket(ticketId);
});
setSuccess(`ยกเลิกรายการแจ้งปัญหาจำนวน ${selectedToCancel.length} รายการสำเร็จ`);
setIsCancelMode(false);
setSelectedToCancel([]);
setConfirmCancelSubmit({ isOpen: false });
// ✅ รอให้ Backend sync ข้อมูลเสร็จ
await refetch();
fetchSidebarCounts();
} catch (error) {
console.error("Error bulk canceling tickets:", error);
setError(error.response?.data?.message || "เกิดข้อผิดพลาดในการยกเลิกรายการ");
setConfirmCancelSubmit({ isOpen: false });
}
};
const handleConfirmCancelSubmit = async () => {
startLoading();
try {
const results = await Promise.allSettled(
selectedToCancel.map(id => ticketService.cancelTicket(id))
);
const succeeded = selectedToCancel.filter((_, i) => results[i].status === 'fulfilled');
succeeded.forEach(ticketId => removeTicket(ticketId));
const failedCount = results.length - succeeded.length;
if (failedCount > 0) {
setError(`ยกเลิกสำเร็จ ${succeeded.length} รายการ, ล้มเหลว ${failedCount} รายการ`);
} else {
setSuccess(`ยกเลิกรายการแจ้งปัญหาจำนวน ${selectedToCancel.length} รายการสำเร็จ`);
}
setIsCancelMode(false);
setSelectedToCancel([]);
setConfirmCancelSubmit({ isOpen: false });
// ✅ รอให้ Backend sync ข้อมูลเสร็จ
await refetch();
fetchSidebarCounts();
} catch (error) {
console.error("Error bulk canceling tickets:", error);
setError(error.response?.data?.message || "เกิดข้อผิดพลาดในการยกเลิกรายการ");
setConfirmCancelSubmit({ isOpen: 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/pages/Tracking.jsx` around lines 194 - 218,
`handleConfirmCancelSubmit` currently uses `Promise.all`, so one rejected
`ticketService.cancelTicket` call prevents successful cancellations in
`selectedToCancel` from being removed locally. Update the bulk cancel flow to
use `Promise.allSettled` (or equivalent per-ticket handling) so you can remove
only the tickets that were actually canceled, then keep the existing
`removeTicket`, `refetch`, and `fetchSidebarCounts` steps in sync with the
partial success state. Make sure `setError` still reports failures for rejected
items while preserving the UI updates for fulfilled cancellations.

Comment on lines +144 to +150
const response = await axios.patch(`/api/ticketManagement/updateTicketStatusAdmin/${ticketId}`, formData, {
withCredentials: true,
headers: {
'Content-Type': 'multipart/form-data'
}
}
);

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 '\n== ticketService.js context ==\n'
if [ -f client/src/services/ticketService.js ]; then
  nl -ba client/src/services/ticketService.js | sed -n '120,170p'
else
  echo "client/src/services/ticketService.js not found"
fi

printf '\n== package.json axios versions ==\n'
for f in package.json client/package.json frontend/package.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f" | grep -n '"axios"' || true
  fi
done

printf '\n== repo-wide multipart/FormData usage ==\n'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' "multipart/form-data|FormData" client/src . 2>/dev/null | sed -n '1,200p'

Repository: CNPJB/trouble-tracking-system-project

Length of output: 1997


🌐 Web query:

axios FormData Content-Type boundary browser do not manually set multipart/form-data documentation

💡 Result:

When using FormData with axios in a browser, web worker, or React Native environment, you should not manually set the Content-Type header [1][2][3]. If you set the Content-Type header manually to multipart/form-data, it often lacks the required multipart boundary string, which causes the request to fail because the browser cannot automatically inject the correct boundary [4][5][6]. The browser runtime needs to generate and append this unique boundary automatically to correctly format the multipart request [1][2][3]. To send FormData correctly, simply pass the FormData instance directly as the payload to your axios request [2][7]: axios.post('https://your-api-endpoint.com/upload', formData); Axios will automatically detect the payload type and ensure the correct Content-Type header (including the necessary boundary) is set [1][2][7]. If you are using shortcut methods such as axios.postForm, axios.putForm, or axios.patchForm, these are designed specifically for this purpose and will handle the headers automatically [8][9][7]. Recent versions of axios include fixes to help prevent issues where manually set, incomplete headers might interfere with this automatic process [5][10][11].

Citations:


Drop the manual multipart Content-Type header
For FormData, let axios/browser set Content-Type so the multipart boundary is added automatically; hard-coding multipart/form-data can break parsing on this upload path.

🤖 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/ticketService.js` around lines 144 - 150, The axios.patch
call in ticketService’s updateTicketStatusAdmin request is manually setting the
multipart Content-Type header for FormData, which can prevent the boundary from
being added correctly. Remove the explicit 'Content-Type': 'multipart/form-data'
header from this upload path and let axios/browser infer it automatically while
keeping the withCredentials option and existing request structure intact.

Comment on lines +103 to +139
select: {
ticketId: true,
category: { select: { ticketCtgName: true } },
location: { select: { locationName: true } },
floor: { select: { floorLevel: true } },
room: { select: { roomId: true, roomName: true } },
equipment: { select: { equipmentName: true } },
title: true,
description: true,
ticketStatus: true,
parentTicketId: true,
adminId: true,
adminNote: true,
rating: true,
comment: true,
createdAt: true,
updatedAt: true,
images: { select: { imageUrl: true, imageType: true } },
ticketCtgId: true,
locationId: true,
floorId: true,
roomId: true,
equipment: { select: { equipmentCode: true } },
upvotes: { select: { userId: true } },
user: { select: { userId: true, fullName: true } },
admin: { select: { userId: true, fullName: true } },
timestampInprogress: true,
timestampFinished: true,
_count: {
select: { subTickets: true }
},
subTickets: {
include: {
user: true
}
}
}

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

Duplicate equipment key silently drops equipmentName from the response.

equipment is defined twice in this select object (line 109: { select: { equipmentName: true } }, line 125: { select: { equipmentCode: true } }). JS object literals keep only the last duplicate key, so only equipmentCode is ever returned — equipmentName is silently lost even though search filters on equipment.equipmentName. Flagged by Biome (noDuplicateObjectKeys).

🐛 Proposed fix: merge into a single key
-                    equipment: { select: { equipmentName: true } },
                     title: true,
@@
-                    equipment: { select: { equipmentCode: true } },
+                    equipment: { select: { equipmentName: true, equipmentCode: true } },
📝 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
select: {
ticketId: true,
category: { select: { ticketCtgName: true } },
location: { select: { locationName: true } },
floor: { select: { floorLevel: true } },
room: { select: { roomId: true, roomName: true } },
equipment: { select: { equipmentName: true } },
title: true,
description: true,
ticketStatus: true,
parentTicketId: true,
adminId: true,
adminNote: true,
rating: true,
comment: true,
createdAt: true,
updatedAt: true,
images: { select: { imageUrl: true, imageType: true } },
ticketCtgId: true,
locationId: true,
floorId: true,
roomId: true,
equipment: { select: { equipmentCode: true } },
upvotes: { select: { userId: true } },
user: { select: { userId: true, fullName: true } },
admin: { select: { userId: true, fullName: true } },
timestampInprogress: true,
timestampFinished: true,
_count: {
select: { subTickets: true }
},
subTickets: {
include: {
user: true
}
}
}
select: {
ticketId: true,
category: { select: { ticketCtgName: true } },
location: { select: { locationName: true } },
floor: { select: { floorLevel: true } },
room: { select: { roomId: true, roomName: true } },
equipment: { select: { equipmentName: true, equipmentCode: true } },
title: true,
description: true,
ticketStatus: true,
parentTicketId: true,
adminId: true,
adminNote: true,
rating: true,
comment: true,
createdAt: true,
updatedAt: true,
images: { select: { imageUrl: true, imageType: true } },
ticketCtgId: true,
locationId: true,
floorId: true,
roomId: true,
upvotes: { select: { userId: true } },
user: { select: { userId: true, fullName: true } },
admin: { select: { userId: true, fullName: true } },
timestampInprogress: true,
timestampFinished: true,
_count: {
select: { subTickets: true }
},
subTickets: {
include: {
user: true
}
}
}
🧰 Tools
🪛 Biome (2.5.1)

[error] 109-109: This property is later overwritten by an object member with the same name.

(lint/suspicious/noDuplicateObjectKeys)

🤖 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/getTicketControllers.js` around lines 103 - 139, The
getTicketControllers response select object has a duplicate equipment key, so
the earlier equipmentName selection is being overwritten and never returned.
Update the ticket query in the controller to merge both equipmentName and
equipmentCode into a single equipment select entry, keeping the existing nested
select shape intact so the response includes all intended equipment fields.

Source: Linters/SAST tools

Comment on lines +336 to +351
const uploadedImagesData = [];
if (ticketStatus === 'resolved' && files && files.length > 0) {
// ใช้เทคนิค Promise.all เหมือนใน addTicket[cite: 27]
const uploadPromises = files.map((file) => uploadToCloudinary(file.buffer, 'TTS-img'));
const cloudinaryResults = await Promise.all(uploadPromises);

cloudinaryResults.forEach((result) => {
uploadedImagesData.push({
imageUrl: result.secure_url,
imageType: "after", // กำหนดว่าเป็นรูป "หลังซ่อม"
imagePublicId: result.public_id,
});
// เก็บ Public ID ไว้เผื่อ Database พัง จะได้ตามไปลบทิ้งได้[cite: 27]
uploadedImagesForRollback.push(result.public_id);
});
}

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

Orphaned Cloudinary uploads on partial Promise.all failure.

If one file in files fails to upload, Promise.all rejects immediately; the .forEach at line 342 that populates uploadedImagesForRollback never runs, so any uploads that did succeed before the failure are never tracked and never cleaned up — leaking storage/cost in Cloudinary indefinitely.

🐛 Proposed fix using allSettled
-            const uploadPromises = files.map((file) => uploadToCloudinary(file.buffer, 'TTS-img'));
-            const cloudinaryResults = await Promise.all(uploadPromises);
-
-            cloudinaryResults.forEach((result) => {
-                uploadedImagesData.push({
-                    imageUrl: result.secure_url,
-                    imageType: "after",
-                    imagePublicId: result.public_id,
-                });
-                uploadedImagesForRollback.push(result.public_id);
-            });
+            const uploadResults = await Promise.allSettled(
+                files.map((file) => uploadToCloudinary(file.buffer, 'TTS-img'))
+            );
+            const failed = uploadResults.filter(r => r.status === 'rejected');
+
+            uploadResults.forEach((r) => {
+                if (r.status === 'fulfilled') {
+                    uploadedImagesData.push({
+                        imageUrl: r.value.secure_url,
+                        imageType: "after",
+                        imagePublicId: r.value.public_id,
+                    });
+                    uploadedImagesForRollback.push(r.value.public_id);
+                }
+            });
+
+            if (failed.length > 0) {
+                await Promise.all(uploadedImagesForRollback.map(deleteFromCloudinary)).catch(() => {});
+                throw new Error("IMAGE_UPLOAD_FAILED");
+            }
📝 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 uploadedImagesData = [];
if (ticketStatus === 'resolved' && files && files.length > 0) {
// ใช้เทคนิค Promise.all เหมือนใน addTicket[cite: 27]
const uploadPromises = files.map((file) => uploadToCloudinary(file.buffer, 'TTS-img'));
const cloudinaryResults = await Promise.all(uploadPromises);
cloudinaryResults.forEach((result) => {
uploadedImagesData.push({
imageUrl: result.secure_url,
imageType: "after", // กำหนดว่าเป็นรูป "หลังซ่อม"
imagePublicId: result.public_id,
});
// เก็บ Public ID ไว้เผื่อ Database พัง จะได้ตามไปลบทิ้งได้[cite: 27]
uploadedImagesForRollback.push(result.public_id);
});
}
const uploadedImagesData = [];
if (ticketStatus === 'resolved' && files && files.length > 0) {
// ใช้เทคนิค Promise.all เหมือนใน addTicket[cite: 27]
const uploadResults = await Promise.allSettled(
files.map((file) => uploadToCloudinary(file.buffer, 'TTS-img'))
);
const failed = uploadResults.filter(r => r.status === 'rejected');
uploadResults.forEach((r) => {
if (r.status === 'fulfilled') {
uploadedImagesData.push({
imageUrl: r.value.secure_url,
imageType: "after", // กำหนดว่าเป็นรูป "หลังซ่อม"
imagePublicId: r.value.public_id,
});
// เก็บ Public ID ไว้เผื่อ Database พัง จะได้ตามไปลบทิ้งได้[cite: 27]
uploadedImagesForRollback.push(r.value.public_id);
}
});
if (failed.length > 0) {
await Promise.all(uploadedImagesForRollback.map(deleteFromCloudinary)).catch(() => {});
throw new Error("IMAGE_UPLOAD_FAILED");
}
}
🤖 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/ticketManagementControllers.js` around lines 336 - 351,
The Cloudinary upload flow in the ticket status update path can leak successful
uploads when one file fails because `Promise.all` rejects before
`cloudinaryResults.forEach` can record rollback IDs. Update the
`uploadToCloudinary` handling in this block to use a failure-tolerant approach
such as `Promise.allSettled` (or equivalent per-file try/catch) so every
successful upload is captured in `uploadedImagesForRollback` and later cleanup
can remove any partial uploads.

Comment on lines +19 to +38
-- AlterTable
ALTER TABLE "tickets" ALTER COLUMN "rating" SET DATA TYPE DOUBLE PRECISION;

-- AddForeignKey
ALTER TABLE "floors" ADD CONSTRAINT "floors_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations"("location_id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "rooms" ADD CONSTRAINT "rooms_floor_id_fkey" FOREIGN KEY ("floor_id") REFERENCES "floors"("floor_id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "equipments" ADD CONSTRAINT "equipments_room_id_fkey" FOREIGN KEY ("room_id") REFERENCES "rooms"("room_id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "ticket_images" ADD CONSTRAINT "ticket_images_ticket_id_fkey" FOREIGN KEY ("ticket_id") REFERENCES "tickets"("ticket_id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "tickets" ADD CONSTRAINT "tickets_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations"("location_id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "tickets" ADD CONSTRAINT "tickets_room_id_fkey" FOREIGN KEY ("room_id") REFERENCES "rooms"("room_id") ON DELETE CASCADE ON UPDATE CASCADE;

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 | 🏗️ Heavy lift

Migration will block reads/writes on tickets, floors, rooms, equipments.

Squawk flags: the column type change takes an ACCESS EXCLUSIVE lock during table rewrite, and all 6 re-added FK constraints lack NOT VALID, each requiring a full table scan under a blocking lock. On tables with meaningful production traffic this can cause request timeouts/outages during deploy.

🔒 Safer migration pattern
--- ALTER TABLE "tickets" ALTER COLUMN "rating" SET DATA TYPE DOUBLE PRECISION;
+-- Note: INT -> DOUBLE PRECISION still requires a table rewrite; consider running
+-- this during a low-traffic window or via a shadow-column + backfill + swap.

--- ALTER TABLE "floors" ADD CONSTRAINT "floors_location_id_fkey" ...;
+ALTER TABLE "floors" ADD CONSTRAINT "floors_location_id_fkey" ... NOT VALID;
+-- run in a separate migration/transaction:
+-- ALTER TABLE "floors" VALIDATE CONSTRAINT "floors_location_id_fkey";

(repeat NOT VALID + VALIDATE CONSTRAINT split for the other 5 FKs)

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 20-20: Changing a column type requires an ACCESS EXCLUSIVE lock on the table which blocks reads and writes while the table is rewritten. Changing the type of the column may also break other clients reading from the table.

(changing-column-type)


[warning] 23-23: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 23-23: Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes to each table. Add NOT VALID to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.

(adding-foreign-key-constraint)


[warning] 26-26: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 26-26: Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes to each table. Add NOT VALID to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.

(adding-foreign-key-constraint)


[warning] 29-29: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 29-29: Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes to each table. Add NOT VALID to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.

(adding-foreign-key-constraint)


[warning] 32-32: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 32-32: Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes to each table. Add NOT VALID to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.

(adding-foreign-key-constraint)


[warning] 35-35: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 35-35: Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes to each table. Add NOT VALID to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.

(adding-foreign-key-constraint)


[warning] 38-38: By default new constraints require a table scan and block writes to the table while that scan occurs. Use NOT VALID with a later VALIDATE CONSTRAINT call.

(constraint-missing-not-valid)


[warning] 38-38: Adding a foreign key constraint requires a table scan and a SHARE ROW EXCLUSIVE lock on both tables, which blocks writes to each table. Add NOT VALID to the constraint in one transaction and then VALIDATE the constraint in a separate transaction.

(adding-foreign-key-constraint)

🤖 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/prisma/migrations/20260606071851_change_rating_to_float/migration.sql`
around lines 19 - 38, The migration in the tickets/floors/rooms/equipments
foreign key section is too blocking for production because the rating type
change and immediate FK creation can take exclusive locks. Update the migration
flow to avoid table-wide blocking by splitting each foreign key add into a NOT
VALID creation followed by a separate VALIDATE CONSTRAINT step, and keep the
tickets.rating type change in mind when ordering operations in this migration
file.

Source: Linters/SAST tools

@CNPJB
CNPJB merged commit dfaf72d into develop Jul 7, 2026
1 check passed
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