Feature/advanced filter - #18
Conversation
|
Warning Review limit reached
Next review available in: 38 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR adds shared advanced filtering, ticket image and status validation, rated resolved-ticket retrieval, responsive ticket galleries, mobile similar-ticket review, and responsive administration layouts. It also updates equipment relations and development-server origins. ChangesTicket entry, status, and image workflows
Shared and administrative filters
Ticket presentation and responsive styling
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
client/src/pages/DetailTicket.jsx (1)
87-95: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMake image gallery controls keyboard-operable.
Clickable
<img>elements and “+ ดูกรูปเพิ่มเติม” text use pointer-only activation. Replace these triggers with focusable labeled buttons, and add dialog support for the popup:role="dialog",aria-modal="true", initial focus, focus return, and Escape-key close.🤖 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 87 - 95, The image gallery triggers in client/src/pages/DetailTicket.jsx lines 87-95 and 124-132, plus client/src/pages/adminPage/IssueManagementDetail.jsx lines 147-164, 179-196, and 205-232, must be keyboard-operable: replace clickable images and “+ ดูกรูปเพิ่มเติม” text with focusable, visibly labeled buttons. Add dialog behavior to each popup with role="dialog", aria-modal="true", initial focus, focus return to the triggering button, and Escape-key closing.client/src/hooks/useResolvedTickets.js (1)
4-30: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd
ratedOnlyto theuseEffectdependency array.The
useEffectat Line 30 depends only on[limit].ratedOnlyis a parameter of the hook but is not tracked as a dependency. With the current caller (Dashboard.jsxpasses fixed literals), this does not manifest as a bug. If a future caller passes aratedOnlyvalue that changes across renders (e.g., from a toggle), the effect will not re-run when onlyratedOnlychanges, and the fetch will use a stale value.🖊️ Proposed fix
fetchResolved(); - }, [limit]); + }, [limit, ratedOnly]);🤖 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/useResolvedTickets.js` around lines 4 - 30, Update the dependency array of the useEffect inside useResolvedTickets to include ratedOnly alongside limit, so the resolved-ticket request re-runs whenever either query parameter changes.
🧹 Nitpick comments (3)
client/src/pages/pageStyles/AddIssue.css (2)
193-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsolidate duplicate media-query rules at 1024px and 820px.
The
@media (max-width: 1024px)block and the@media (max-width: 820px)block define the same rules for.add-issue-containerand.form-row. Because 820px is narrower than 1024px, the 820px block is redundant; the 1024px rules already apply at that width. Merge them into a single breakpoint, or add the distinct rules the 820px block was meant to introduce.♻️ Proposed fix
`@media` (max-width: 1024px) { .add-issue-container { grid-template-columns: 1fr; } .form-row { flex-direction: column; gap: 15px; } } -@media (max-width: 820px) { - .add-issue-container { - grid-template-columns: 1fr; - } - - .form-row { - flex-direction: column; - gap: 15px; - } -} - `@media` (max-width: 430px) {🤖 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 193 - 213, Remove the redundant `@media` (max-width: 820px) block in the responsive styles, keeping the shared .add-issue-container and .form-row rules under the existing `@media` (max-width: 1024px) breakpoint; only retain a separate 820px block if distinct narrower-screen rules are required.
106-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove unused
.urgent-labelstyling.
.urgent-labelis only defined inclient/src/pages/pageStyles/AddIssue.css; no rendered element inclient/src/pages/AddIssue.jsxapplies it. Remove the selector if it is not needed.🤖 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 106 - 109, Remove the unused .urgent-label selector and its color declaration from AddIssue.css, since AddIssue.jsx does not apply this class.server/prisma/migrations/20260731145100_add_floor_location_to_equipments/migration.sql (1)
10-13: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy liftVerify the foreign-key lock window.
Each
ADD CONSTRAINTcan scan existing rows and block writes onequipmentsand the referenced table. If low downtime is required, add the foreign keys asNOT VALIDand validate them in a separate migration. Otherwise, document the maintenance window.🤖 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/20260731145100_add_floor_location_to_equipments/migration.sql` around lines 10 - 13, Update the migration’s foreign-key creation for equipments_floor_id_fkey and equipments_location_id_fkey to use a low-downtime approach: add each constraint as NOT VALID, then validate them separately in a follow-up migration. If the migration system cannot support this sequence, document the required maintenance window instead.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/componentsAdmin/AdminLayout.jsx`:
- Line 3: Update the React import in AdminLayout to remove the invalid lowercase
children named import, retaining only the imports actually used by the component
and avoiding the nonexistent React export.
In `@client/src/components/componentsAdmin/Adminsidebar.css`:
- Around line 96-105: Update the `@media` (max-width: 430px) rules in
Adminsidebar.css so .admin-sidebar remains hidden while .mobile-sidebar is
displayed. Remove or override the !important hide declaration for
.mobile-sidebar to keep mobile navigation visible at this breakpoint.
In `@client/src/components/componentsAdmin/TicketActionPanel.jsx`:
- Around line 41-47: Update the ImageUploader rendered by TicketActionPanel to
pass minImages={1}, aligning its required-image indicator with the existing
handleResolve validation and the AddIssue/EditIssue usage.
In `@client/src/components/componentsStyles/AdvancedFilterPanel.css`:
- Around line 18-33: Update the .btn-toggle-filter CSS block and the related
declarations through the additionally affected section to remove blank lines
between declaration groups. Rename the fadeInDown keyframe to fade-in-down and
update every animation reference to use the new kebab-case name.
In `@client/src/components/componentsStyles/SimilarTickets.css`:
- Line 149: Replace the deprecated word-break declaration with overflow-wrap:
anywhere in the .ticket-info p rule in
client/src/components/componentsStyles/SimilarTickets.css:149-149 and all five
occurrences in client/src/components/componentsStyles/CardPendingProblem.css at
30-30, 103-103, 123-123, and 134-134.
In `@client/src/components/TIcketStatusFilter.jsx`:
- Around line 6-22: Update the default all-status handling in TicketStatusFilter
so omitted allOptionValue represents every status currently shown by
displayStatuses, including filtered allowedStatuses. Prefer deriving the value
from displayStatuses, while preserving an explicitly provided allOptionValue.
In `@client/src/pages/AddIssue.jsx`:
- Around line 230-235: Reset hasBypassedSimilar whenever the form fields used to
determine similarTickets change, and also clear it in handleCancelSubmit so
bypassing one submission does not disable future duplicate checks. Keep the
existing mobile modal condition in the similar-ticket guard and preserve the
bypass behavior for the current submission.
- Around line 452-461: Remove the empty div with className "form-group"
immediately before the ImageUploader component, leaving ImageUploader and its
existing props unchanged.
In `@client/src/pages/adminPage/AuditIssues.css`:
- Line 216: Remove the empty .panel-content-wrapper rule from
client/src/pages/adminPage/AuditIssues.css at lines 216-216 and the empty
.admin-sidebar rule from client/src/components/componentsAdmin/Adminsidebar.css
at lines 86-86; no replacement styles are needed.
- Around line 127-162: Update the mobile drawer styles in .audit-right-panel and
.panel-overlay to stay within the viewport: replace the 120vh/120vw dimensions
with viewport-bounded sizing such as 100dvh, and use inset: 0 for the overlay’s
fixed positioning while preserving the existing drawer behavior and layout.
In `@client/src/pages/adminPage/AuditIssues.jsx`:
- Around line 255-259: Add an aria-label describing the close action to the
button rendering FaTimes in the panel-close-header, while preserving its
existing setIsMobilePanelOpen(false) click behavior.
In `@client/src/pages/adminPage/IssueManagement.css`:
- Around line 267-271: Update the .issue-management-container rule in the
max-width: 820px media query to use a fluid width constrained by a maximum
instead of the fixed 525px width, preventing horizontal overflow on intermediate
viewports while preserving the intended layout limit.
In `@client/src/pages/adminPage/IssueManagement.jsx`:
- Around line 86-91: Update handleClearAllFilters to also disable the My Tasks
state by resetting isMyTasksOnly and remove adminId from the filters passed to
updateFilters, while preserving the existing category, location, and status
resets.
- Around line 51-53: Update the hasActiveFilter calculation in IssueManagement
to include selectedStatus when it represents a non-default status, while
preserving the existing checks for searchKeyword, selectedCategory,
selectedLocation, and isMyTasksOnly.
In `@client/src/pages/adminPage/IssueManagementDetail.css`:
- Around line 438-463: Remove the fixed width and minimum-width constraints from
.info-card-detail and .action-card, and update .detail-layout so it switches to
a single-column grid at a breakpoint where both panels cannot fit. Ensure the
`@media` (max-width: 820px) rules preserve the single-column layout and prevent
horizontal overflow.
- Around line 61-89: The status classes in IssueManagementDetail.css use white
text with insufficient contrast on several default backgrounds. Update the
affected .status-pending, .status-in_progress, .status-resolved,
.status-rejected, and .status-duplicate rules by using accessible darker
backgrounds or dark text, and ensure the resolved --status-* custom-property
values also meet a 4.5:1 contrast ratio.
In `@client/src/pages/EditIssue.jsx`:
- Around line 328-342: Update the title field in the EditIssue form,
identifiable by name="title" and formData.title, to enforce maxLength={20} and
display the same character counter behavior used by AddIssue.jsx. Preserve the
existing required, value, and change-handler behavior.
In `@client/src/pages/Tracking.jsx`:
- Around line 65-68: Update handleTabChange so that when it applies baseFilters
and resets the query status to allowedStatuses.join(','), it also sets
selectedStatus to that same all-status value. Keep the select value and
activeDropdownFiltersCount synchronized with the reset filters.
In `@server/controllers/ticketControllers.js`:
- Around line 448-475: Update the auto-unmerge logic in the transaction to
capture the result of the `tx.ticket.updateMany` call and use its `count` field
for `releasedSubTicketsCount`. Replace the current
`ticketToCancel._count.subTickets` value while preserving the existing filter
and response messaging.
---
Outside diff comments:
In `@client/src/hooks/useResolvedTickets.js`:
- Around line 4-30: Update the dependency array of the useEffect inside
useResolvedTickets to include ratedOnly alongside limit, so the resolved-ticket
request re-runs whenever either query parameter changes.
In `@client/src/pages/DetailTicket.jsx`:
- Around line 87-95: The image gallery triggers in
client/src/pages/DetailTicket.jsx lines 87-95 and 124-132, plus
client/src/pages/adminPage/IssueManagementDetail.jsx lines 147-164, 179-196, and
205-232, must be keyboard-operable: replace clickable images and “+
ดูกรูปเพิ่มเติม” text with focusable, visibly labeled buttons. Add dialog
behavior to each popup with role="dialog", aria-modal="true", initial focus,
focus return to the triggering button, and Escape-key closing.
---
Nitpick comments:
In `@client/src/pages/pageStyles/AddIssue.css`:
- Around line 193-213: Remove the redundant `@media` (max-width: 820px) block in
the responsive styles, keeping the shared .add-issue-container and .form-row
rules under the existing `@media` (max-width: 1024px) breakpoint; only retain a
separate 820px block if distinct narrower-screen rules are required.
- Around line 106-109: Remove the unused .urgent-label selector and its color
declaration from AddIssue.css, since AddIssue.jsx does not apply this class.
In
`@server/prisma/migrations/20260731145100_add_floor_location_to_equipments/migration.sql`:
- Around line 10-13: Update the migration’s foreign-key creation for
equipments_floor_id_fkey and equipments_location_id_fkey to use a low-downtime
approach: add each constraint as NOT VALID, then validate them separately in a
follow-up migration. If the migration system cannot support this sequence,
document the required maintenance window instead.
🪄 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 Plus
Run ID: c4eca366-92b7-4232-b58e-0945be4d247b
📒 Files selected for processing (49)
client/src/components/AdvancedFilterPanel.jsxclient/src/components/EquipmentCategoryFilter.jsxclient/src/components/ImageUploader.jsxclient/src/components/SimilarTickets.jsxclient/src/components/TIcketStatusFilter.jsxclient/src/components/TicketActionMenu.jsxclient/src/components/TicketCategoryFilter.jsxclient/src/components/TicketLocationFilter.jsxclient/src/components/componentsAdmin/AdminLayout.jsxclient/src/components/componentsAdmin/Adminsidebar.cssclient/src/components/componentsAdmin/MergeManagementPanel.cssclient/src/components/componentsAdmin/MergeManagementPanel.jsxclient/src/components/componentsAdmin/TicketActionPanel.jsxclient/src/components/componentsAdmin/ToggleSwitch.jsxclient/src/components/componentsStyles/AdvancedFilterPanel.cssclient/src/components/componentsStyles/CardPendingProblem.cssclient/src/components/componentsStyles/FilterProblem.cssclient/src/components/componentsStyles/ImageUploader.cssclient/src/components/componentsStyles/SimilarTickets.cssclient/src/components/componentsStyles/StatisticsSidebar.cssclient/src/components/componentsStyles/TicketCategoryFilter.cssclient/src/components/componentsStyles/TicketLocationFilter.cssclient/src/components/componentsStyles/TicketStatusFilter.cssclient/src/hooks/useMasterData.jsclient/src/hooks/useResolvedTickets.jsclient/src/pages/AddIssue.jsxclient/src/pages/Dashboard.jsxclient/src/pages/DetailTicket.jsxclient/src/pages/EditIssue.jsxclient/src/pages/Tracking.jsxclient/src/pages/adminPage/AssetManagement.cssclient/src/pages/adminPage/AssetManagement.jsxclient/src/pages/adminPage/AuditIssues.cssclient/src/pages/adminPage/AuditIssues.jsxclient/src/pages/adminPage/IssueManagement.cssclient/src/pages/adminPage/IssueManagement.jsxclient/src/pages/adminPage/IssueManagementDetail.cssclient/src/pages/adminPage/IssueManagementDetail.jsxclient/src/pages/pageStyles/AddIssue.cssclient/src/pages/pageStyles/DetailTicket.cssclient/src/pages/pageStyles/EditIssue.cssclient/src/pages/pageStyles/Tracking.cssclient/vite.config.jsserver/controllers/getTicketControllers.jsserver/controllers/ticketControllers.jsserver/controllers/ticketManagementControllers.jsserver/prisma/migrations/20260731145100_add_floor_location_to_equipments/migration.sqlserver/prisma/schema.prismaserver/src/index.js
| import { AdminSidebar } from "./AdminSidebar.jsx"; | ||
| import { Outlet } from 'react-router-dom'; | ||
| import React , {children} from "react"; | ||
| import React, { children } from "react"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
curl -fsSL https://unpkg.com/react@19.2.4/cjs/react.development.js |
rg -n 'exports\.(Children|children)\s*='
# Expect `exports.Children`; do not expect `exports.children`.Repository: CNPJB/trouble-tracking-system-project
Length of output: 210
Remove the invalid children named import.
React exports Children, not lowercase children; this named import fails during module loading.
Proposed fix
-import React, { children } from "react";
+import React from "react";📝 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.
| import React, { children } from "react"; | |
| import React from "react"; |
🤖 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/AdminLayout.jsx` at line 3, Update the
React import in AdminLayout to remove the invalid lowercase children named
import, retaining only the imports actually used by the component and avoiding
the nonexistent React export.
| @media (max-width: 430px) { | ||
| .admin-sidebar { | ||
| display: none !important; /* สั่งโชว์เมนูคอมพิวเตอร์ */ | ||
| } | ||
| .mobile-sidebar { | ||
| display: none !important; /* สั่งโชว์เมนูมือถือ */ | ||
| } | ||
| .admin-sidebar { | ||
| display: none !important; | ||
| /* สั่งโชว์เมนูคอมพิวเตอร์ */ | ||
| } | ||
|
|
||
| .mobile-sidebar { | ||
| display: none !important; | ||
| /* สั่งโชว์เมนูมือถือ */ | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep the mobile navigation visible below 430px.
Lines 97-104 hide both .admin-sidebar and .mobile-sidebar. Users cannot navigate the administration area at this breakpoint.
Hide the desktop sidebar, but display the mobile sidebar.
`@media` (max-width: 430px) {
.admin-sidebar {
display: none !important;
}
.mobile-sidebar {
- display: none !important;
+ display: block !important;
}
}📝 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.
| @media (max-width: 430px) { | |
| .admin-sidebar { | |
| display: none !important; /* สั่งโชว์เมนูคอมพิวเตอร์ */ | |
| } | |
| .mobile-sidebar { | |
| display: none !important; /* สั่งโชว์เมนูมือถือ */ | |
| } | |
| .admin-sidebar { | |
| display: none !important; | |
| /* สั่งโชว์เมนูคอมพิวเตอร์ */ | |
| } | |
| .mobile-sidebar { | |
| display: none !important; | |
| /* สั่งโชว์เมนูมือถือ */ | |
| } | |
| `@media` (max-width: 430px) { | |
| .admin-sidebar { | |
| display: none !important; | |
| /* สั่งโชว์เมนูคอมพิวเตอร์ */ | |
| } | |
| .mobile-sidebar { | |
| display: block !important; | |
| /* สั่งโชว์เมนูมือถือ */ | |
| } |
🤖 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/Adminsidebar.css` around lines 96 -
105, Update the `@media` (max-width: 430px) rules in Adminsidebar.css so
.admin-sidebar remains hidden while .mobile-sidebar is displayed. Remove or
override the !important hide declaration for .mobile-sidebar to keep mobile
navigation visible at this breakpoint.
| const handleResolve = () => { | ||
| if (selectedImages.length === 0) { | ||
| setErrorMsg('กรุณาอัปโหลดรูปภาพอย่างน้อย 1 รูปเพื่อเป็นหลักฐานการซ่อมแซม'); | ||
| return; | ||
| } | ||
| setErrorMsg(''); | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Pass minImages={1} to ImageUploader to match the new validation.
handleResolve requires at least one image before allowing resolution. The ImageUploader rendered at Line 164 does not receive minImages, so it defaults to 0 and shows only the "ไม่เกิน 3 รูป" label. The user sees no required-image indicator until the error message appears after clicking "บันทึกและปิดงาน". AddIssue.jsx and EditIssue.jsx both pass minImages={1} for the same requirement.
🖊️ Proposed fix
<ImageUploader
selectedImages={selectedImages}
fileInputRef={fileInputRef}
onImageChange={handleImageChange}
onRemoveImage={removeImage}
maxImages={3}
+ minImages={1}
/>🤖 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/TicketActionPanel.jsx` around lines 41
- 47, Update the ImageUploader rendered by TicketActionPanel to pass
minImages={1}, aligning its required-image indicator with the existing
handleResolve validation and the AddIssue/EditIssue usage.
| .btn-toggle-filter { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 8px; | ||
| padding: 12px 20px; | ||
|
|
||
| border-radius: 30px; | ||
| border: 2px solid #e0e0e0; | ||
| background-color: #ffffff; | ||
|
|
||
| font-size: 15px; | ||
| color: #334155; | ||
|
|
||
| cursor: pointer; | ||
| transition: all 0.3s ease; | ||
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Resolve the Stylelint errors.
Stylelint rejects the blank declaration spacing in .btn-toggle-filter. It also rejects the camel-case fadeInDown keyframe name. Remove the blank lines, rename the keyframe to fade-in-down, and update the animation reference.
Also applies to: 82-119
🧰 Tools
🪛 Stylelint (17.14.1)
[error] 24-24: Expected no empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 28-28: Expected no empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 31-31: 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/AdvancedFilterPanel.css` around lines
18 - 33, Update the .btn-toggle-filter CSS block and the related declarations
through the additionally affected section to remove blank lines between
declaration groups. Rename the fadeInDown keyframe to fade-in-down and update
every animation reference to use the new kebab-case name.
Source: Linters/SAST tools
| -webkit-line-clamp: 1; | ||
| -webkit-box-orient: vertical; | ||
| overflow: hidden; | ||
| word-break: break-word; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- CSS files ---\n'
cat -n client/src/components/componentsStyles/SimilarTickets.css | sed -n '140,156p'
printf '\n--- CardPendingProblem occurrences ---\n'
cat -n client/src/components/componentsStyles/CardPendingProblem.css | sed -n '24,140p'
printf '\n--- Stylelint config references ---\n'
for f in package.json .stylelintrc .stylelintrc.json .stylelintrc.js .stylelintrc.yml .stylelintrc.yaml .stylelint.config.js; do
if [ -f "$f" ]; then
echo "### $f"
sed -n '1,240p' "$f"
fi
done
printf '\n--- package stylelint deps ---\n'
if [ -f package.json ]; then
node - <<'JS'
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('package.json','utf8'));
for (const sec of ['dependencies','devDependencies']) {
if (p[sec]) {
for (const [k,v] of Object.entries(p[sec])) {
if (k.includes('stylelint') || k.includes('css')) console.log(sec, k, v);
}
}
}
JS
fiRepository: CNPJB/trouble-tracking-system-project
Length of output: 266
🌐 Web query:
MDN CSS word-break break-word deprecated overflow-wrap anywhere
💡 Result:
The word-break: break-word value is officially deprecated and should be avoided in modern CSS [1][2]. It is recommended to use overflow-wrap: anywhere instead [2]. Key technical details: 1. Deprecation Status: The break-word value for the word-break property is deprecated by the W3C CSS Text Module Level 3 specification [3][4]. Authors are advised against using it in new style sheets [4]. 2. Functional Equivalence: When word-break: break-word is used, it behaves as if overflow-wrap: anywhere and word-break: normal were both applied [1][3]. It essentially forces a line break at an arbitrary point within an unbreakable string (such as a long URL or word) if that string would otherwise overflow its container [5][6]. 3. Why overflow-wrap: anywhere is preferred: - overflow-wrap: anywhere is the modern, standardized way to achieve this wrapping behavior [6]. - Unlike overflow-wrap: break-word, the anywhere value considers the length of the string when calculating min-content intrinsic sizes, which helps prevent flex containers from expanding beyond their intended limits [5][6]. - It is broadly supported across all modern browsers [7]. If you need to support the deprecated behavior using standard properties, you can achieve the same effect by combining overflow-wrap: anywhere and word-break: normal [1][8].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/word-break
- 2: https://web-platform-dx.github.io/web-features-explorer/features/word-break-break-word/
- 3: https://stackoverflow.com/questions/17259916/difference-between-overflow-wrap-and-word-break
- 4: [css-text-3] Normatively disallow break-word w3c/csswg-drafts#4087
- 5: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/overflow-wrap
- 6: https://github.com/mdn/content/blob/main/files/en-us/web/css/reference/properties/overflow-wrap/index.md
- 7:
word-break: break-word;vsoverflow-wrap: anywhere;mdn/content#15676 - 8: "word-break: normal;" !== "word-break: break-word;" mdn/content#26948
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- SimilarTickets occurrences ---'
sed -n '140,156p' client/src/components/componentsStyles/SimilarTickets.css | cat -n
printf '%s\n' ''
printf '%s\n' '--- CardPendingProblem occurrences ---'
sed -n '24,140p' client/src/components/componentsStyles/CardPendingProblem.css | cat -n
printf '%s\n' ''
printf '%s\n' '--- all word-break: break-word occurrences ---'
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' 'word-break\s*:\s*break-word' .
printf '%s\n' ''
printf '%s\n' '--- stylelint config references ---'
for f in package.json .stylelintrc .stylelintrc.json .stylelintrc.js .stylelintrc.yml .stylelintrc.yaml .stylelint.config.js; do
if [ -f "$f" ]; then
echo "### $f"
sed -n '1,240p' "$f"
fi
done
printf '%s\n' ''
printf '%s\n' '--- package stylelint deps ---'
if [ -f package.json ]; then
node - <<'JS'
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('package.json','utf8'));
for (const sec of ['dependencies','devDependencies']) {
if (p[sec]) {
for (const [k,v] of Object.entries(p[sec])) {
if (k.toLowerCase().includes('stylelint') || k.toLowerCase().includes('css')) console.log(sec, k, v);
}
}
}
JS
fiRepository: CNPJB/trouble-tracking-system-project
Length of output: 4362
Replace word-break: break-word with overflow-wrap: anywhere.
word-break: break-word is deprecated, while overflow-wrap: anywhere provides the same modern behavior. Replace it in one .ticket-info p rule and all five CardPendingProblem.css occurrences.
🧰 Tools
🪛 Stylelint (17.14.1)
[error] 149-149: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)
(declaration-property-value-keyword-no-deprecated)
📍 Affects 2 files
client/src/components/componentsStyles/SimilarTickets.css#L149-L149(this comment)client/src/components/componentsStyles/CardPendingProblem.css#L30-L30client/src/components/componentsStyles/CardPendingProblem.css#L103-L103client/src/components/componentsStyles/CardPendingProblem.css#L123-L123client/src/components/componentsStyles/CardPendingProblem.css#L134-L134
🤖 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/SimilarTickets.css` at line 149,
Replace the deprecated word-break declaration with overflow-wrap: anywhere in
the .ticket-info p rule in
client/src/components/componentsStyles/SimilarTickets.css:149-149 and all five
occurrences in client/src/components/componentsStyles/CardPendingProblem.css at
30-30, 103-103, 123-123, and 134-134.
Source: Linters/SAST tools
| .status-pending { | ||
| background-color: var(--status-pending-bg, #f59e0b); | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| .status-in_progress { | ||
| background-color: var(--status-in-progress-bg, #3b82f6); | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| .status-resolved { | ||
| background-color: var(--status-resolved-bg, #10b981); | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| .status-rejected { | ||
| background-color: var(--status-reject-bg, #ef4444); | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| .status-canceled { | ||
| background-color: var(--status-canceled-bg, #6b7280); | ||
| color: #ffffff; | ||
| } | ||
|
|
||
| .status-duplicate { | ||
| background-color: var(--status-duplicate-bg, #8b5cf6); | ||
| color: #ffffff; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use status colors with sufficient text contrast.
The default white text does not have sufficient contrast on #f59e0b, #3b82f6, #10b981, #ef4444, and #8b5cf6. Use darker backgrounds or dark text that meets a 4.5:1 contrast ratio. Validate the resolved --status-* values too.
🤖 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/IssueManagementDetail.css` around lines 61 - 89,
The status classes in IssueManagementDetail.css use white text with insufficient
contrast on several default backgrounds. Update the affected .status-pending,
.status-in_progress, .status-resolved, .status-rejected, and .status-duplicate
rules by using accessible darker backgrounds or dark text, and ensure the
resolved --status-* custom-property values also meet a 4.5:1 contrast ratio.
| width: 750px; | ||
| } | ||
|
|
||
| .detail-layout { | ||
| gap: 15px; | ||
| max-width: 750px; | ||
| } | ||
| } | ||
|
|
||
| @media (max-width: 820px) { | ||
| .info-card-detail { | ||
| max-width: 520px; | ||
| } | ||
|
|
||
| .issue-detail-container { | ||
| padding: 20px 0; | ||
| } | ||
|
|
||
| .ticket-img-admin-zone { | ||
| grid-template-columns: 1fr; | ||
| gap: 20px; | ||
| } | ||
|
|
||
| .gallery-admin { | ||
| grid-template-columns: repeat(2, 1fr); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Remove the incompatible fixed layout widths.
At this breakpoint, .detail-layout has max-width: 750px, .info-card-detail has width: 750px, and .action-card has min-width: 400px. The two grid columns and gap cannot fit in 750px. The page will overflow horizontally on common desktop and tablet widths. The 820px breakpoint also keeps the two-column layout.
Remove the fixed card width. Set the layout to a single column before its available width cannot contain both panels.
🤖 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/IssueManagementDetail.css` around lines 438 - 463,
Remove the fixed width and minimum-width constraints from .info-card-detail and
.action-card, and update .detail-layout so it switches to a single-column grid
at a breakpoint where both panels cannot fit. Ensure the `@media` (max-width:
820px) rules preserve the single-column layout and prevent horizontal overflow.
| <label>ประเภทปัญหา <span style={{ color: 'red' }}>*</span></label> | ||
| <select name="categoryId" onChange={handleChange} value={formData.categoryId} required> | ||
| <option value="">เลือกประเภทปัญหา</option> | ||
| {activeCategories.map(c => <option key={c.ticketCtgId} value={c.ticketCtgId}>{c.ticketCtgName}</option>)} | ||
| </select> | ||
| </div> | ||
| <div className="form-group"> | ||
| <label>หัวข้อปัญหา <span>*</span></label> | ||
| <label>หัวข้อปัญหา <span style={{ color: 'red' }}>*</span></label> | ||
| <input type="text" name="title" onChange={handleChange} value={formData.title} required /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="form-row"> | ||
| <div className="form-group"> | ||
| <label>สถานที่ <span>*</span></label> | ||
| <label>สถานที่ <span style={{ color: 'red' }}>*</span></label> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add the same 20-character title limit used in AddIssue.jsx.
AddIssue.jsx now enforces maxLength={20} on the title field and shows a character counter. The title field here (Line 336) has neither. A user can edit an existing ticket's title past 20 characters through this form, producing data that is inconsistent with tickets created through AddIssue.jsx and that may not fit UI elements sized for the 20-character limit.
🖊️ Proposed fix
<div className="form-group">
- <label>หัวข้อปัญหา <span style={{ color: 'red' }}>*</span></label>
- <input type="text" name="title" onChange={handleChange} value={formData.title} required />
+ <div className="label-with-counter">
+ <label>หัวข้อปัญหา <span style={{ color: 'red' }}>*</span></label>
+ <span className="char-counter">{formData.title.length}/20</span>
+ </div>
+ <input type="text" name="title" onChange={handleChange} value={formData.title} maxLength={20} required />
</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.
| <label>ประเภทปัญหา <span style={{ color: 'red' }}>*</span></label> | |
| <select name="categoryId" onChange={handleChange} value={formData.categoryId} required> | |
| <option value="">เลือกประเภทปัญหา</option> | |
| {activeCategories.map(c => <option key={c.ticketCtgId} value={c.ticketCtgId}>{c.ticketCtgName}</option>)} | |
| </select> | |
| </div> | |
| <div className="form-group"> | |
| <label>หัวข้อปัญหา <span>*</span></label> | |
| <label>หัวข้อปัญหา <span style={{ color: 'red' }}>*</span></label> | |
| <input type="text" name="title" onChange={handleChange} value={formData.title} required /> | |
| </div> | |
| </div> | |
| <div className="form-row"> | |
| <div className="form-group"> | |
| <label>สถานที่ <span>*</span></label> | |
| <label>สถานที่ <span style={{ color: 'red' }}>*</span></label> | |
| <label>ประเภทปัญหา <span style={{ color: 'red' }}>*</span></label> | |
| <select name="categoryId" onChange={handleChange} value={formData.categoryId} required> | |
| <option value="">เลือกประเภทปัญหา</option> | |
| {activeCategories.map(c => <option key={c.ticketCtgId} value={c.ticketCtgId}>{c.ticketCtgName}</option>)} | |
| </select> | |
| </div> | |
| <div className="form-group"> | |
| <div className="label-with-counter"> | |
| <label>หัวข้อปัญหา <span style={{ color: 'red' }}>*</span></label> | |
| <span className="char-counter">{formData.title.length}/20</span> | |
| </div> | |
| <input type="text" name="title" onChange={handleChange} value={formData.title} maxLength={20} required /> | |
| </div> | |
| </div> | |
| <div className="form-row"> | |
| <div className="form-group"> | |
| <label>สถานที่ <span style={{ color: 'red' }}>*</span></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/EditIssue.jsx` around lines 328 - 342, Update the title
field in the EditIssue form, identifiable by name="title" and formData.title, to
enforce maxLength={20} and display the same character counter behavior used by
AddIssue.jsx. Preserve the existing required, value, and change-handler
behavior.
| const activeDropdownFiltersCount = | ||
| (selectedCategory ? 1 : 0) + | ||
| (selectedLocation ? 1 : 0) + | ||
| (selectedStatus !== 'pending,in_progress,duplicate,resolved,canceled,rejected' ? 1 : 0); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Synchronize the status select when a tab resets filters.
handleTabChange resets the query status to allowedStatuses.join(','), but it does not reset selectedStatus. After a user selects rejected and changes tabs, the list contains all statuses while the select and active-filter badge still show rejected.
Set selectedStatus to the same all-status value when handleTabChange applies baseFilters.
Proposed fix
const handleTabChange = (tabId) => {
setActiveTab(tabId);
+ setSelectedStatus(allowedStatuses.join(','));
const baseFilters = {Also applies to: 261-275
🤖 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 65 - 68, Update handleTabChange
so that when it applies baseFilters and resets the query status to
allowedStatuses.join(','), it also sets selectedStatus to that same all-status
value. Keep the select value and activeDropdownFiltersCount synchronized with
the reset filters.
| // Auto-Unmerge: หากตั๋วใบนี้มีลูก ให้ทำการปลดแอกตั๋วลูกทั้งหมด | ||
| if (ticketToCancel._count.subTickets > 0) { | ||
| await tx.ticket.updateMany({ | ||
| where: { | ||
| parentTicketId: id, | ||
| ticketStatus: 'duplicate' | ||
| }, | ||
| data: { | ||
| ticketStatus: 'pending', | ||
| parentTicketId: null, | ||
| updatedAt: new Date() | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| return { | ||
| canceledTicket, | ||
| releasedSubTicketsCount: ticketToCancel._count.subTickets | ||
| }; | ||
| }); | ||
|
|
||
| res.status(200).json({ | ||
| success: true, | ||
| message: "Cancel ticket Successfully!", | ||
| data: canceledTicket | ||
| message: result.releasedSubTicketsCount > 0 | ||
| ? `ยกเลิกรายการสำเร็จ และระบบได้แยกตั๋วที่ถูกรวม ${result.releasedSubTicketsCount} รายการกลับสู่กระดาน` | ||
| : "ยกเลิกรายการแจ้งปัญหาสำเร็จ", | ||
| data: result.canceledTicket | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Derive releasedSubTicketsCount from the actual updateMany result, not from the total child count.
Line 465 reports ticketToCancel._count.subTickets, which counts all children with parentTicketId: id regardless of status. The updateMany at Lines 450-461 only releases children with ticketStatus: 'duplicate'. If a subticket's status ever diverges from 'duplicate' while the parent is still pending, the reported count would not match the number of tickets actually released. Use the updateMany result's count field so the message always matches the actual mutation.
🖊️ Proposed fix
- // Auto-Unmerge: หากตั๋วใบนี้มีลูก ให้ทำการปลดแอกตั๋วลูกทั้งหมด
- if (ticketToCancel._count.subTickets > 0) {
- await tx.ticket.updateMany({
- where: {
- parentTicketId: id,
- ticketStatus: 'duplicate'
- },
- data: {
- ticketStatus: 'pending',
- parentTicketId: null,
- updatedAt: new Date()
- }
- });
- }
-
- return {
- canceledTicket,
- releasedSubTicketsCount: ticketToCancel._count.subTickets
- };
+ // Auto-Unmerge: หากตั๋วใบนี้มีลูก ให้ทำการปลดแอกตั๋วลูกทั้งหมด
+ let releasedSubTicketsCount = 0;
+ if (ticketToCancel._count.subTickets > 0) {
+ const releaseResult = await tx.ticket.updateMany({
+ where: {
+ parentTicketId: id,
+ ticketStatus: 'duplicate'
+ },
+ data: {
+ ticketStatus: 'pending',
+ parentTicketId: null,
+ updatedAt: new Date()
+ }
+ });
+ releasedSubTicketsCount = releaseResult.count;
+ }
+
+ return {
+ canceledTicket,
+ releasedSubTicketsCount
+ };📝 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.
| // Auto-Unmerge: หากตั๋วใบนี้มีลูก ให้ทำการปลดแอกตั๋วลูกทั้งหมด | |
| if (ticketToCancel._count.subTickets > 0) { | |
| await tx.ticket.updateMany({ | |
| where: { | |
| parentTicketId: id, | |
| ticketStatus: 'duplicate' | |
| }, | |
| data: { | |
| ticketStatus: 'pending', | |
| parentTicketId: null, | |
| updatedAt: new Date() | |
| } | |
| }); | |
| } | |
| return { | |
| canceledTicket, | |
| releasedSubTicketsCount: ticketToCancel._count.subTickets | |
| }; | |
| }); | |
| res.status(200).json({ | |
| success: true, | |
| message: "Cancel ticket Successfully!", | |
| data: canceledTicket | |
| message: result.releasedSubTicketsCount > 0 | |
| ? `ยกเลิกรายการสำเร็จ และระบบได้แยกตั๋วที่ถูกรวม ${result.releasedSubTicketsCount} รายการกลับสู่กระดาน` | |
| : "ยกเลิกรายการแจ้งปัญหาสำเร็จ", | |
| data: result.canceledTicket | |
| }); | |
| // Auto-Unmerge: หากตั๋วใบนี้มีลูก ให้ทำการปลดแอกตั๋วลูกทั้งหมด | |
| let releasedSubTicketsCount = 0; | |
| if (ticketToCancel._count.subTickets > 0) { | |
| const releaseResult = await tx.ticket.updateMany({ | |
| where: { | |
| parentTicketId: id, | |
| ticketStatus: 'duplicate' | |
| }, | |
| data: { | |
| ticketStatus: 'pending', | |
| parentTicketId: null, | |
| updatedAt: new Date() | |
| } | |
| }); | |
| releasedSubTicketsCount = releaseResult.count; | |
| } | |
| return { | |
| canceledTicket, | |
| releasedSubTicketsCount | |
| }; | |
| }); | |
| res.status(200).json({ | |
| success: true, | |
| message: result.releasedSubTicketsCount > 0 | |
| ? `ยกเลิกรายการสำเร็จ และระบบได้แยกตั๋วที่ถูกรวม ${result.releasedSubTicketsCount} รายการกลับสู่กระดาน` | |
| : "ยกเลิกรายการแจ้งปัญหาสำเร็จ", | |
| data: result.canceledTicket | |
| }); |
🤖 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` around lines 448 - 475, Update the
auto-unmerge logic in the transaction to capture the result of the
`tx.ticket.updateMany` call and use its `count` field for
`releasedSubTicketsCount`. Replace the current
`ticketToCancel._count.subTickets` value while preserving the existing filter
and response messaging.
Add feature and fix migration file.
Summary by CodeRabbit
New Features
Bug Fixes