Feature/ticket watchlist system#2862
Conversation
|
@jainiksha is attempting to deploy a commit to the ritesh Team on Vercel. A member of the Team first needs to authorize it. |
📝 Walkthrough
WalkthroughThis PR adds multiple independent UI features across the helpdesk frontend: a ticket activity heatmap in admin analytics, dashboard KPI refactoring with resolved/pending counts, a follow-up reminder card in admin ticket detail, SLA risk and AI resolution summary panels, accessibility insights in settings, AI recommendations during ticket creation, saved filter views in MyTickets, dependency detection in user ticket detail, a watch button in the mobile app, and ticket timeline tracking in the store. Two backend Git LFS model pointer entries are also removed. ChangesTicket Store Timeline Tracking
Admin Dashboard and Analytics
User-facing Ticket Views
Mobile App
Backend Model Artifacts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 4❌ Failed checks (4 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (6)
Frontend/src/admin/pages/AdminDashboard.jsx-10-10 (1)
10-10:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove unused recharts imports.
The recharts components imported on this line are not referenced anywhere in the component. This adds unnecessary bundle size.
🧹 Proposed fix
-import {ResponsiveContainer,LineChart,Line,XAxis,YAxis,Tooltip,CartesianGrid} from "recharts";🤖 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 `@Frontend/src/admin/pages/AdminDashboard.jsx` at line 10, Review the AdminDashboard component to identify which recharts components from the import statement (ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid) are actually used in the code. Remove any components that are not referenced anywhere in the component from the import statement to reduce unnecessary bundle size.Frontend/src/user/pages/CreateTicket.jsx-467-467 (1)
467-467:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAlign the visibility condition with the fetch logic.
The panel displays when
issue.length > 15, but the useEffect at line 62 clears recommendations whenissue.trim().length < 15. This mismatch means that typing 20 spaces will show the panel with "No similar issues found," even though no fetch occurred.🔧 Align the condition
-{issue.length > 15 && ( +{issue.trim().length >= 15 && ( <div className="mt-4 rounded-2xl border border-emerald-100 bg-emerald-50 p-4">🤖 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 `@Frontend/src/user/pages/CreateTicket.jsx` at line 467, The visibility condition for the recommendations panel at line 467 uses `issue.length > 15` while the useEffect at line 62 uses `issue.trim().length < 15` to control when recommendations are fetched. This mismatch allows the panel to display with stale content when the input contains only spaces. Align the visibility condition by changing `issue.length > 15` to `issue.trim().length > 15` to match the fetch logic and ensure the panel only displays when there is actual non-whitespace content to search.Frontend/src/user/pages/MyTickets.jsx-134-154 (1)
134-154:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPrevent duplicate view names.
Users can currently save multiple views with identical names, making it impossible to distinguish them in the UI and potentially causing React key conflicts.
🔧 Add duplicate name check
const saveCurrentView = () => { if (!viewName.trim()) return; + + if (savedViews.some(v => v.name === viewName.trim())) { + setError('A view with this name already exists.'); + return; + } const newView = { - name: viewName, + name: viewName.trim(), searchQuery, statusFilter, priorityFilter };🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 134 - 154, The saveCurrentView function allows users to save multiple views with identical names, which causes UI issues and React key conflicts. Add a validation check before creating the newView object to verify that no existing view in savedViews already has the same name as the one being saved (compare against view.name for each item in the savedViews array). If a duplicate name is found, return early from the function to prevent saving. This ensures each saved view has a unique name within the savedViews collection.Frontend/src/user/pages/MyTickets.jsx-258-277 (1)
258-277:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEnsure unique keys for saved view items.
Using
key={view.name}will produce duplicate React keys if users save multiple views with the same name (which is currently allowed per line 134). This violates React's key uniqueness requirement and can cause rendering bugs.🔧 Use index as fallback or generate unique IDs
Short-term fix using index (acceptable after duplicate name prevention is added):
-{savedViews.map((view) => ( +{savedViews.map((view, index) => ( <div - key={view.name} + key={`${view.name}-${index}`} className="flex items-center gap-2" >Better long-term: add a unique
idfield when creating views (e.g.,Date.now()orcrypto.randomUUID()).🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 258 - 277, The savedViews.map function uses key={view.name} which creates duplicate React keys if multiple views share the same name, causing potential rendering bugs. Replace this with a unique identifier: either use the index from the map callback as a fallback (acceptable once duplicate name prevention is implemented), or preferably add a unique id field (such as Date.now() or crypto.randomUUID()) to each view object when it is created, then use key={view.id} instead of key={view.name}.Frontend/src/user/pages/TicketDetail.jsx-16-16 (1)
16-16:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winConsolidate duplicate import.
AlertTriangleandLink2are imported separately on line 16, but they should be added to the existinglucide-reactimport block at lines 3-7.🔧 Proposed fix
import { ArrowLeft, Clock, Bot, UserCog, ShieldCheck, Calendar, Zap, Image as ImageIcon, MessageSquare, - RotateCcw, Loader2, CheckCircle2, History + RotateCcw, Loader2, CheckCircle2, History, + AlertTriangle, Link2 } from 'lucide-react'; import { formatFullTimestamp } from '../../utils/dateUtils'; import { supabase } from "../../lib/supabaseClient"; import { Card } from "../../components/ui/card"; import TicketStatusBadge from "../components/TicketStatusBadge"; import TicketTimeline from "../components/TicketTimeline"; import TicketChat from "../../components/shared/TicketChat"; import { formatTicketId } from "../../utils/format"; import CSATModal from "../components/CSATModal"; -import { AlertTriangle, Link2 } from 'lucide-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 `@Frontend/src/user/pages/TicketDetail.jsx` at line 16, The imports `AlertTriangle` and `Link2` from 'lucide-react' are being imported separately instead of being consolidated with the existing lucide-react import block. Add `AlertTriangle` and `Link2` to the existing lucide-react import statement that appears earlier in the file, then remove the duplicate import statement that contains only these two icons.Frontend/src/user/pages/TicketDetail.jsx-126-128 (1)
126-128:⚠️ Potential issue | 🟡 MinorRemove or implement dependency tracking, or clarify if these fields are placeholder for future use.
The code reads
ticket.metadata?.blocked_byandticket.metadata?.related_ticketand displays them in the UI, but these fields are never populated anywhere in the codebase—not in the backend ticket creation/save endpoint, AI analysis pipeline, or any migrations. Themetadatafield is typed asJson | nullwith no documented schema for these dependency fields. Either implement the backend logic to populate these fields when tickets have dependencies, or remove the related UI code if this feature is not yet supported. If this is intentional placeholder code, add a comment clarifying the status.🤖 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 `@Frontend/src/user/pages/TicketDetail.jsx` around lines 126 - 128, The variables dependencyTicket and relatedTicket are populated from ticket.metadata?.blocked_by and ticket.metadata?.related_ticket fields that are never populated in the backend codebase. Either implement the backend logic to populate these metadata fields when creating or updating tickets with dependency information, or remove the references to dependencyTicket and relatedTicket from the TicketDetail component UI code and related dependent code. If this is intentional placeholder code for future implementation, add a TODO comment explaining the planned feature instead.
🧹 Nitpick comments (11)
MobileApp/src/screens/user/TicketDetailScreen.js (1)
142-149: ⚡ Quick winMissing accessibility properties for the watch button.
The watch button lacks accessibility properties that are important for React Native components to ensure screen reader compatibility and proper accessibility labels.
♿ Proposed fix to add accessibility properties
<TouchableOpacity style={styles.watchBtn} onPress={() => setIsWatching(!isWatching)} + accessibilityRole="button" + accessibilityLabel={isWatching ? "Stop watching this ticket" : "Watch this ticket"} + accessibilityHint="Toggle notifications for ticket updates" > <Text style={styles.watchBtnText}> {isWatching ? "Watching" : "Watch"} </Text> </TouchableOpacity>🤖 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 `@MobileApp/src/screens/user/TicketDetailScreen.js` around lines 142 - 149, The TouchableOpacity component with the watch button (styled with styles.watchBtn and onPress handler toggling isWatching) is missing accessibility properties needed for screen reader compatibility. Add accessibility properties to this component including accessibilityLabel to provide a descriptive label of the button's purpose, accessibilityHint to provide additional context about what the button does, accessibilityRole set to "button" to identify it as an interactive element, and accessibilityState to indicate the current state (watched or unwatched) so screen readers can properly convey the button's state to users.Frontend/src/admin/pages/AdminDashboard.jsx (1)
205-223: ⚡ Quick winAdd navigation handlers or remove button wrappers.
The "Resolved Tickets" and "Pending Tickets" cards are wrapped in
<button>elements withoutonClickhandlers, unlike the other KPI cards (Total, Active, AI Auto-Resolved) which navigate to filtered views. This creates an inconsistent user experience where some cards appear clickable but do nothing.💡 Suggested fix (add navigation)
-<button className="text-left group focus:outline-none"> +<button onClick={() => navigate('/admin/tickets?filter=resolved')} className="text-left group focus:outline-none"> <StatCard label="Resolved Tickets" value={metrics.resolvedTickets} color="emerald" subtitle="Successfully completed" customIcon={<TicketIcon />} /> </button> -<button className="text-left group focus:outline-none"> +<button onClick={() => navigate('/admin/tickets?filter=pending')} className="text-left group focus:outline-none"> <StatCard label="Pending Tickets" value={metrics.pendingTickets} color="amber" subtitle="Waiting for action" customIcon={<ActivityIcon />} /> </button>Or remove the button wrappers entirely if navigation 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 `@Frontend/src/admin/pages/AdminDashboard.jsx` around lines 205 - 223, The button elements wrapping the "Resolved Tickets" and "Pending Tickets" StatCard components lack onClick handlers, creating inconsistency with other KPI cards (Total, Active, AI Auto-Resolved) which navigate to filtered views. Either add onClick handlers to these buttons to navigate to the respective filtered ticket views (matching the pattern of the other cards), or remove the button wrappers entirely if navigation functionality is not intended for these cards. Ensure the final state is consistent across all KPI cards in terms of interactivity and visual feedback.Frontend/src/admin/pages/AdminAnalytics.jsx (1)
300-352: ⚡ Quick winAdd day labels to improve heatmap readability.
The heatmap uses a 24-column grid that automatically wraps into 7 rows for the days of the week, but there are no visible labels indicating which row represents which day. Users can only see day names in the tooltip on hover, making the heatmap harder to interpret at a glance.
💡 Suggested enhancement
Add day labels to the left side of the heatmap, similar to typical calendar heatmaps (like GitHub's contribution graph):
<div className="overflow-x-auto"> + <div className="flex gap-2"> + <div className="flex flex-col gap-1 justify-around text-xs text-gray-500 pr-2"> + {DAYS.map(day => ( + <div key={day} className="h-[22px] flex items-center">{day}</div> + ))} + </div> <div className="grid grid-cols-24 gap-1 min-w-[700px]"> {stats.heatmapData?.map((cell, index) => ( <div key={index} - title={`${days[cell.day]} ${cell.hour}:00 → ${cell.count} tickets`} + title={`${DAYS[cell.day]} ${cell.hour}:00 → ${cell.count} tickets`} style={{ width: '22px', height: '22px', borderRadius: '4px', background: cell.count === 0 ? '`#f3f4f6`' : cell.count < 3 ? '`#bbf7d0`' : cell.count < 6 ? '`#4ade80`' : '`#15803d`' }} /> ))} </div> + </div> </div>🤖 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 `@Frontend/src/admin/pages/AdminAnalytics.jsx` around lines 300 - 352, The heatmap lacks visible day-of-week labels, making it difficult to interpret which row corresponds to which day without hovering over cells. Wrap the grid container in a flex layout that includes a left column for day labels. Create this label column by mapping through the days array (the same array used in the title tooltip attribute) and displaying each day name aligned with its corresponding heatmap row. Ensure the day labels are positioned to the left of the heatmap grid and vertically centered with their respective rows for clarity.Frontend/src/user/pages/MyTickets.jsx (3)
239-279: ⚡ Quick winImprove accessibility for saved view controls.
The view name input lacks a label, and the apply/delete buttons have no accessible names for screen readers.
♿ Add accessibility attributes
<div className="flex items-center gap-2"> + <label htmlFor="view-name-input" className="sr-only"> + Saved View Name + </label> <input + id="view-name-input" type="text" placeholder="View Name" value={viewName} onChange={(e) => setViewName(e.target.value)} className="px-3 py-2 border rounded-lg text-sm" /> <button onClick={saveCurrentView} className="px-4 py-2 bg-emerald-600 text-white rounded-lg" + aria-label="Save current filters as a new view" > Save View </button> </div> {savedViews.length > 0 && ( <div className="flex gap-2 flex-wrap"> {savedViews.map((view, index) => ( <div key={`${view.name}-${index}`} className="flex items-center gap-2" > <button onClick={() => applySavedView(view.name)} className="px-3 py-1 bg-gray-100 rounded-lg text-sm" + aria-label={`Apply saved view: ${view.name}`} > {view.name} </button> <button onClick={() => deleteView(view.name)} className="text-red-500 text-xs" + aria-label={`Delete saved view: ${view.name}`} > ✕ </button> </div> ))} </div> )}🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 239 - 279, The view name input field and the action buttons for saved views lack proper accessibility features. Add an associated label for the text input field that accepts the view name (currently with placeholder "View Name"), and add aria-label attributes to the button that applies a saved view (onClick={() => applySavedView(view.name)}) and the button that deletes a view (onClick={() => deleteView(view.name)}) so that screen readers can properly announce their purposes to users.
156-166: 💤 Low valueRemove or utilize the unused
selectedViewstate.
setSelectedView(name)is called when applying a view, butselectedViewis never read or used to provide visual feedback in the UI. This is dead code.Consider either:
- Removing the
selectedViewstate entirely if it's not needed, or- Using it to highlight the currently active view in the UI:
💡 Example: Highlight active view
<button onClick={() => applySavedView(view.name)} - className="px-3 py-1 bg-gray-100 rounded-lg text-sm" + className={`px-3 py-1 rounded-lg text-sm ${ + selectedView === view.name + ? 'bg-emerald-100 text-emerald-700 font-bold' + : 'bg-gray-100' + }`} > {view.name} </button>🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 156 - 166, The `setSelectedView(name)` call in the `applySavedView` function sets state that is never read or used to provide visual feedback in the UI, making it dead code. Either remove the `setSelectedView(name)` call and the corresponding `selectedView` state variable entirely if visual indication of the active view is not needed, or utilize the `selectedView` state by comparing it against view names in the UI to highlight the currently active saved view with a visual indicator such as a CSS class or styling.
270-275: ⚡ Quick winAdd confirmation before deleting saved views.
Users can accidentally delete a view with a single click and no recovery option. A confirmation dialog reduces the risk of data loss.
🛡️ Add delete confirmation
<button - onClick={() => deleteView(view.name)} + onClick={() => { + if (window.confirm(`Delete saved view "${view.name}"?`)) { + deleteView(view.name); + } + }} className="text-red-500 text-xs" aria-label={`Delete saved view: ${view.name}`} > ✕ </button>🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 270 - 275, The delete button for saved views in the MyTickets component currently triggers deleteView immediately without any user confirmation. Modify the onClick handler in the button element to prompt the user for confirmation before executing the deleteView call. Wrap the deleteView(view.name) invocation with a confirmation dialog that asks the user to verify they want to delete the view, proceeding only if they confirm the action.Frontend/src/user/pages/CreateTicket.jsx (3)
483-495: ⚡ Quick winReplace index-based keys with stable identifiers.
Using
key={index}can cause React to misidentify items when the recommendations array changes, leading to incorrect rendering or lost component state.♻️ Use a stable key
If the API returns unique IDs:
-{recommendations.map((item, index) => ( +{recommendations.map((item) => ( <div - key={index} + key={item.id} className="bg-white rounded-xl p-3 border border-emerald-100" >Otherwise, construct a stable key from the content:
{recommendations.map((item, index) => ( <div - key={index} + key={`${item.title}-${index}`} className="bg-white rounded-xl p-3 border border-emerald-100" >🤖 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 `@Frontend/src/user/pages/CreateTicket.jsx` around lines 483 - 495, The recommendations.map function is using key={index} which is an unstable identifier that causes React to misidentify items when the array changes. Replace the index-based key with a stable identifier from the item object. If the API response includes a unique ID property on each item, use that (like key={item.id}). If no unique ID exists, construct a stable key from the item content itself, such as combining item.title with item.solution or using another immutable property that uniquely identifies each recommendation.
467-503: ⚡ Quick winAnnounce dynamic recommendations to screen readers.
When recommendations load or change, screen readers are not notified. Adding an
aria-liveregion ensures assistive technologies announce the updates.♿ Add aria-live region
{issue.trim().length >= 15 && ( - <div className="mt-4 rounded-2xl border border-emerald-100 bg-emerald-50 p-4"> + <div + className="mt-4 rounded-2xl border border-emerald-100 bg-emerald-50 p-4" + aria-live="polite" + aria-atomic="true" + > <div className="flex items-center gap-2 mb-3"> <BrainCircuit size={18} className="text-emerald-600" /> <h4 className="font-bold text-emerald-700">🤖 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 `@Frontend/src/user/pages/CreateTicket.jsx` around lines 467 - 503, The recommendations section in CreateTicket.jsx does not announce content changes to screen readers. Add an aria-live attribute with a value of "polite" to the main container div that wraps the conditional rendering of the loading state, recommendations list, and empty state message (the div with className containing "mt-4 rounded-2xl border border-emerald-100 bg-emerald-50 p-4"). This will ensure that when isFetchingRecommendations changes or recommendations load, assistive technologies will announce the updates to users.
61-92: Plan for async cleanup when integrating the real API.The cleanup function currently only clears the debounce timer. When replacing the mock data with an actual API call, add an
AbortControllerto handle in-flight request cancellation and prevent state updates after unmount. This is especially important since the effect re-runs on everyissuechange—rapid user input could create race conditions without proper cleanup.The codebase already uses this pattern (see
AIProcessing.jsxlines 126-137), so follow that established convention.💡 Recommended async cleanup pattern
useEffect(() => { if (issue.trim().length < 15) { setRecommendations([]); return; } + + const abortController = new AbortController(); const timer = setTimeout(async () => { try { setIsFetchingRecommendations(true); - // Replace with actual API later - const mockRecommendations = [...] + const response = await fetch('/api/recommendations', { + method: 'POST', + signal: abortController.signal, + body: JSON.stringify({ issue }) + }); + const data = await response.json(); - setRecommendations(mockRecommendations); + setRecommendations(data.recommendations); } catch (error) { + if (error.name === 'AbortError') return; console.error(error); + // Show user-facing error message } finally { setIsFetchingRecommendations(false); } }, 800); - return () => clearTimeout(timer); + return () => { + clearTimeout(timer); + abortController.abort(); + }; }, [issue]);🤖 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 `@Frontend/src/user/pages/CreateTicket.jsx` around lines 61 - 92, The useEffect hook in CreateTicket.jsx currently only clears the debounce timer in its cleanup function. When replacing the mock recommendations with an actual API call, add an AbortController instance at the start of the effect, pass its signal to the API request, and call abort() in the cleanup function to cancel any in-flight requests. This prevents race conditions and state updates after unmount, especially since the effect re-runs frequently on issue changes. Follow the established pattern already implemented in AIProcessing.jsx (lines 126-137) to maintain consistency across the codebase.Frontend/src/pages/TicketDetailView.jsx (1)
224-242: 💤 Low valueConsider more specific fallback messaging.
The AI Resolution Summary card uses a generic fallback message when
ticket.resolution_summaryis not available. While functional, this default text ("This issue has been successfully resolved...") doesn't add value beyond what the user already knows from the resolved status.Consider either:
- Omitting the card entirely when
resolution_summaryis missing- Providing a more actionable message like "No detailed resolution summary is available for this ticket."
💡 Alternative approach
-{isResolved && ( +{isResolved && ticket.resolution_summary && ( <Card className="p-0 overflow-hidden border-none shadow-xl shadow-gray-200/50"> <CardHeader className="bg-emerald-50 px-8 py-5 border-b border-emerald-100"> <CardTitle className="font-bold text-emerald-800 flex items-center gap-2"> <BrainCircuit size={18} /> AI Resolution Summary </CardTitle> </CardHeader> <CardContent className="p-8"> <div className="bg-emerald-50 border border-emerald-100 rounded-xl p-4"> <p className="text-gray-700 leading-relaxed"> - {ticket.resolution_summary || - "This issue has been successfully resolved by the support team. All reported concerns were reviewed and appropriate actions were taken to address the problem."} + {ticket.resolution_summary} </p> </div> </CardContent> </Card> )}🤖 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 `@Frontend/src/pages/TicketDetailView.jsx` around lines 224 - 242, The AI Resolution Summary card in TicketDetailView.jsx currently displays a generic fallback message when ticket.resolution_summary is not available. This message lacks value since it merely restates what the user already knows from the resolved status. Modify the logic around the Card that checks isResolved to also verify that ticket.resolution_summary exists before rendering the entire card, OR if you prefer to always show the card, replace the current fallback string with a more specific and actionable message such as "No detailed resolution summary is available for this ticket." to better communicate the actual situation to the user.Frontend/src/user/pages/TicketDetail.jsx (1)
188-202: ⚡ Quick winMake dependency ticket IDs clickable for better UX.
Dependency ticket IDs are displayed as plain text in multiple locations (blocked alert card, field values, dependencies card), but users cannot navigate to them. This creates a frustrating experience when users want to view the blocking or related tickets.
🔗 Suggested enhancement: Add navigation links
Create clickable links that navigate to the dependency tickets:
{isBlocked && ( <Card className="p-4 border-amber-200 bg-amber-50"> <div className="flex items-center gap-3"> <AlertTriangle className="w-5 h-5 text-amber-600" /> <div> <p className="font-bold text-amber-800"> Ticket Dependency Detected </p> <p className="text-sm text-amber-700"> - This ticket is blocked by Ticket #{dependencyTicket} + This ticket is blocked by{' '} + <button + onClick={() => navigate(`/ticket/${dependencyTicket}`)} + className="font-bold underline hover:text-amber-900" + > + Ticket #{dependencyTicket} + </button> </p> </div> </div> </Card> )}Apply similar changes to lines 223, 232, and 354-356.
Also applies to: 223-223, 232-232, 354-356
🤖 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 `@Frontend/src/user/pages/TicketDetail.jsx` around lines 188 - 202, Replace plain text displays of dependency ticket IDs with clickable navigation links to improve UX. In the blocked alert card (where dependencyTicket is shown), convert the Ticket #{dependencyTicket} reference into a clickable link that navigates to that ticket's detail page. Apply the same treatment to all other locations where dependency ticket IDs are displayed as plain text (field values and dependencies card). Use a consistent link styling and ensure navigation occurs when users click these ticket ID references.
🤖 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 `@Frontend/src/admin/pages/AdminAnalytics.jsx`:
- Around line 124-165: The days array is currently defined inside the useMemo
hook but needs to be accessible in the JSX render section outside of that hook.
Move the days array definition (containing the day name strings 'Sun', 'Mon',
'Tue', etc.) outside and above the useMemo hook where the heatmapData is being
calculated. This will make it available in the component's scope for use both
within and outside the memoized calculation, preventing the ReferenceError when
the heatmap render tries to access the day names.
- Around line 139-141: Replace the direct new Date(ticket.created_at)
instantiation at lines 139-141 with a call to formatTimelineDate(), which is
already used at line 120 of the same component for timeline data extraction.
This ensures consistent timezone handling throughout the component so that both
the heatmap and timeline display activity using the same timezone adjustment
logic (UTC forcing for timestamps without timezone markers). Extract the day and
hour values from the result of formatTimelineDate() instead of from the raw Date
object.
In `@Frontend/src/admin/pages/AdminDashboard.jsx`:
- Line 276: The issue is that formatTimelineDate(new Date()) in the
AdminDashboard component displays the current render time rather than when the
dashboard data was actually fetched, causing the "Last Synced" timestamp to
constantly update on every render. To fix this, store the actual fetch timestamp
in component state when the data is retrieved (likely in a useEffect hook where
your data fetching occurs), and then pass that stored fetch timestamp to
formatTimelineDate() instead of creating a new Date object on each render.
In `@Frontend/src/admin/pages/AdminSettings.jsx`:
- Around line 172-256: The Accessibility Insights Card component in
AdminSettings.jsx displays hardcoded static values like "92%" and fixed status
messages without performing actual accessibility measurements, which could
mislead administrators about real WCAG compliance status. Address this by
either: (1) integrating an actual accessibility testing library such as axe-core
or pa11y to dynamically calculate and display real accessibility metrics and
recommendations instead of the hardcoded "Accessibility Score" percentage and
status values, (2) adding a clear visual indicator or banner labeling the entire
section as "Example" or "Placeholder" to make it clear these are not real
measurements, or (3) removing the entire Card section until real accessibility
testing capabilities are available. Choose the approach that best fits your
project roadmap and implement accordingly to ensure administrators are not
misled about actual accessibility compliance.
In `@Frontend/src/admin/pages/AdminTicketDetail.jsx`:
- Around line 181-203: The handleSendReminder function updates only local React
state without making any API call to actually send the reminder or persist the
last_reminder_sent metadata to the database. This means the reminder data will
be lost on page refresh. Add an API call (e.g., using fetch or your HTTP client)
before the success toast in handleSendReminder to persist the updated metadata
with the last_reminder_sent timestamp to the database. Wrap the state update and
toast in a .then() block after the API call succeeds, and ensure errors from the
API call are caught in the catch block.
In `@Frontend/src/pages/TicketDetailView.jsx`:
- Around line 171-187: The SLA Risk panel is incorrectly nested as a child
element inside the Current Status div instead of being a separate grid item. To
fix this, close the Current Status div (around line 170) before the SLA Risk div
begins, making SLA Risk a sibling grid item. Additionally, update the parent
grid container at line 157 from md:grid-cols-2 to md:grid-cols-3 to properly
accommodate three columns for the Assigned Team, Current Status, and SLA Risk
panels.
In `@Frontend/src/store/ticketStore.js`:
- Around line 128-130: The activeTicket object update is missing the timeline
entry that gets added to the tickets array. When shouldUpdateActive is true and
appending a message, the activeTicket update needs to include both the new
message in the messages array AND the timeline entry (similar to what happens in
lines 109-121 for the tickets array). Modify the activeTicket update to append
both the message and the corresponding timeline entry to maintain state
consistency between the tickets array and the activeTicket object.
- Around line 28-44: The timeline data structure is implemented inconsistently:
ticketStore.js creates timeline as an array of event objects, but
TicketTimeline.jsx expects timeline to be an object indexed by pipeline stage
keys (created, ai_analyzed, triaged, metadata_harvested, routed,
resolution_started). Fix the following four locations in
Frontend/src/store/ticketStore.js: (1) In addTicket method (lines 28-44),
replace the timeline array initialization with an object containing a created
key mapped to the ISO timestamp. (2) In the first updateTicket method (lines
48-69), replace the timeline array spread pattern with an object merge pattern
that uses the appropriate stage key (e.g., routed or triaged) based on the
operation. (3) In the second updateTicket method handling activeTicket (lines
77-96), apply the same object merge pattern instead of array spreading. (4) In
the appendMessage method (lines 109-121), remove the timeline mutation entirely
since message comments should not update pipeline stage timestamps.
In `@Frontend/src/user/pages/MyTickets.jsx`:
- Around line 148-151: Add error handling to both localStorage write operations
in MyTickets.jsx. In the saveCurrentView function at lines 148-151, wrap the
localStorage.setItem call in a try-catch block that catches QuotaExceededError
and other storage exceptions; on failure, revert the updatedViews state change
and display an error message to the user such as "Unable to save view. Storage
quota may be exceeded." In the deleteView function at lines 174-177, similarly
wrap the localStorage.setItem call in a try-catch block; on failure, revert the
savedViews state change and notify the user of the failure. Both sites must
restore their respective state objects to their previous values before the
failed write attempt and provide user-facing feedback.
- Around line 168-178: The deleteView function's localStorage.setItem call lacks
error handling and can throw exceptions. Wrap the localStorage.setItem operation
inside a try-catch block to handle any potential errors (similar to how it
should be handled in saveCurrentView). In the catch block, log the error
appropriately so failures to persist view deletions to localStorage are properly
captured and don't cause the application to crash.
- Around line 61-67: The useEffect hook that calls JSON.parse on localStorage
data lacks error handling and validation, which can crash the component if the
stored JSON is corrupted or invalid. Wrap the JSON.parse call and
localStorage.getItem access in a try-catch block to handle any errors that occur
during parsing. Additionally, add validation after parsing to ensure the
retrieved data is actually an array with the expected structure, and provide a
fallback to an empty array if parsing fails, validation fails, or if there is
any error. Pass this validated result to setSavedViews.
- Around line 134-154: The saveCurrentView function does not handle potential
exceptions from localStorage.setItem, which can throw a QuotaExceededError when
storage quota is full, causing the save operation to crash without user
feedback. Wrap the localStorage.setItem call in a try-catch block to handle
storage exceptions gracefully. In the catch block, handle the error
appropriately (such as displaying a user-friendly error message to inform the
user that the view could not be saved due to storage limitations) and consider
whether to revert the state update if localStorage persistence fails.
In `@Frontend/src/user/pages/TicketDetail.jsx`:
- Around line 218-234: The "Blocked By" and "Related Ticket" field divs are
currently positioned outside of the Ticket Details Card container, making them
float unstyled in the layout. Move these two div blocks that contain the
dependency fields (the ones displaying dependencyTicket and relatedTicket
values) from their current location to inside the Ticket Details Card container,
positioning them after the existing fields within the card's grid layout to
maintain visual consistency and proper containment.
In `@MobileApp/src/screens/user/TicketDetailScreen.js`:
- Line 25: The isWatching state is initialized to false but never updates from
the backend data. Add a useEffect hook in the TicketDetailScreen component that
runs on mount and fetches the watch status from the backend for the current
ticket and user. Parse the response and call setIsWatching with the actual watch
status retrieved from the server, ensuring the button displays the correct state
based on whether the user is already watching this ticket.
- Around line 142-149: The watch button toggle in the TouchableOpacity component
with the onPress handler only updates local state and does not persist the watch
action to the backend. To fix this, you need to: (1) create a `ticket_watchers`
table in the database to store which users are watching which tickets, including
a unique constraint on ticket_id and user_id; (2) create backend API endpoints
for adding and removing watchers that handle the user-ticket watch relationship;
(3) replace the onPress handler that currently calls setIsWatching(!isWatching)
with an actual API call to add or remove the current user as a watcher based on
the current isWatching state; (4) integrate the watch functionality with your
notification system so that users watching a ticket receive notifications when
the ticket status changes, comments are added, or assignments are updated.
---
Minor comments:
In `@Frontend/src/admin/pages/AdminDashboard.jsx`:
- Line 10: Review the AdminDashboard component to identify which recharts
components from the import statement (ResponsiveContainer, LineChart, Line,
XAxis, YAxis, Tooltip, CartesianGrid) are actually used in the code. Remove any
components that are not referenced anywhere in the component from the import
statement to reduce unnecessary bundle size.
In `@Frontend/src/user/pages/CreateTicket.jsx`:
- Line 467: The visibility condition for the recommendations panel at line 467
uses `issue.length > 15` while the useEffect at line 62 uses
`issue.trim().length < 15` to control when recommendations are fetched. This
mismatch allows the panel to display with stale content when the input contains
only spaces. Align the visibility condition by changing `issue.length > 15` to
`issue.trim().length > 15` to match the fetch logic and ensure the panel only
displays when there is actual non-whitespace content to search.
In `@Frontend/src/user/pages/MyTickets.jsx`:
- Around line 134-154: The saveCurrentView function allows users to save
multiple views with identical names, which causes UI issues and React key
conflicts. Add a validation check before creating the newView object to verify
that no existing view in savedViews already has the same name as the one being
saved (compare against view.name for each item in the savedViews array). If a
duplicate name is found, return early from the function to prevent saving. This
ensures each saved view has a unique name within the savedViews collection.
- Around line 258-277: The savedViews.map function uses key={view.name} which
creates duplicate React keys if multiple views share the same name, causing
potential rendering bugs. Replace this with a unique identifier: either use the
index from the map callback as a fallback (acceptable once duplicate name
prevention is implemented), or preferably add a unique id field (such as
Date.now() or crypto.randomUUID()) to each view object when it is created, then
use key={view.id} instead of key={view.name}.
In `@Frontend/src/user/pages/TicketDetail.jsx`:
- Line 16: The imports `AlertTriangle` and `Link2` from 'lucide-react' are being
imported separately instead of being consolidated with the existing lucide-react
import block. Add `AlertTriangle` and `Link2` to the existing lucide-react
import statement that appears earlier in the file, then remove the duplicate
import statement that contains only these two icons.
- Around line 126-128: The variables dependencyTicket and relatedTicket are
populated from ticket.metadata?.blocked_by and ticket.metadata?.related_ticket
fields that are never populated in the backend codebase. Either implement the
backend logic to populate these metadata fields when creating or updating
tickets with dependency information, or remove the references to
dependencyTicket and relatedTicket from the TicketDetail component UI code and
related dependent code. If this is intentional placeholder code for future
implementation, add a TODO comment explaining the planned feature instead.
---
Nitpick comments:
In `@Frontend/src/admin/pages/AdminAnalytics.jsx`:
- Around line 300-352: The heatmap lacks visible day-of-week labels, making it
difficult to interpret which row corresponds to which day without hovering over
cells. Wrap the grid container in a flex layout that includes a left column for
day labels. Create this label column by mapping through the days array (the same
array used in the title tooltip attribute) and displaying each day name aligned
with its corresponding heatmap row. Ensure the day labels are positioned to the
left of the heatmap grid and vertically centered with their respective rows for
clarity.
In `@Frontend/src/admin/pages/AdminDashboard.jsx`:
- Around line 205-223: The button elements wrapping the "Resolved Tickets" and
"Pending Tickets" StatCard components lack onClick handlers, creating
inconsistency with other KPI cards (Total, Active, AI Auto-Resolved) which
navigate to filtered views. Either add onClick handlers to these buttons to
navigate to the respective filtered ticket views (matching the pattern of the
other cards), or remove the button wrappers entirely if navigation functionality
is not intended for these cards. Ensure the final state is consistent across all
KPI cards in terms of interactivity and visual feedback.
In `@Frontend/src/pages/TicketDetailView.jsx`:
- Around line 224-242: The AI Resolution Summary card in TicketDetailView.jsx
currently displays a generic fallback message when ticket.resolution_summary is
not available. This message lacks value since it merely restates what the user
already knows from the resolved status. Modify the logic around the Card that
checks isResolved to also verify that ticket.resolution_summary exists before
rendering the entire card, OR if you prefer to always show the card, replace the
current fallback string with a more specific and actionable message such as "No
detailed resolution summary is available for this ticket." to better communicate
the actual situation to the user.
In `@Frontend/src/user/pages/CreateTicket.jsx`:
- Around line 483-495: The recommendations.map function is using key={index}
which is an unstable identifier that causes React to misidentify items when the
array changes. Replace the index-based key with a stable identifier from the
item object. If the API response includes a unique ID property on each item, use
that (like key={item.id}). If no unique ID exists, construct a stable key from
the item content itself, such as combining item.title with item.solution or
using another immutable property that uniquely identifies each recommendation.
- Around line 467-503: The recommendations section in CreateTicket.jsx does not
announce content changes to screen readers. Add an aria-live attribute with a
value of "polite" to the main container div that wraps the conditional rendering
of the loading state, recommendations list, and empty state message (the div
with className containing "mt-4 rounded-2xl border border-emerald-100
bg-emerald-50 p-4"). This will ensure that when isFetchingRecommendations
changes or recommendations load, assistive technologies will announce the
updates to users.
- Around line 61-92: The useEffect hook in CreateTicket.jsx currently only
clears the debounce timer in its cleanup function. When replacing the mock
recommendations with an actual API call, add an AbortController instance at the
start of the effect, pass its signal to the API request, and call abort() in the
cleanup function to cancel any in-flight requests. This prevents race conditions
and state updates after unmount, especially since the effect re-runs frequently
on issue changes. Follow the established pattern already implemented in
AIProcessing.jsx (lines 126-137) to maintain consistency across the codebase.
In `@Frontend/src/user/pages/MyTickets.jsx`:
- Around line 239-279: The view name input field and the action buttons for
saved views lack proper accessibility features. Add an associated label for the
text input field that accepts the view name (currently with placeholder "View
Name"), and add aria-label attributes to the button that applies a saved view
(onClick={() => applySavedView(view.name)}) and the button that deletes a view
(onClick={() => deleteView(view.name)}) so that screen readers can properly
announce their purposes to users.
- Around line 156-166: The `setSelectedView(name)` call in the `applySavedView`
function sets state that is never read or used to provide visual feedback in the
UI, making it dead code. Either remove the `setSelectedView(name)` call and the
corresponding `selectedView` state variable entirely if visual indication of the
active view is not needed, or utilize the `selectedView` state by comparing it
against view names in the UI to highlight the currently active saved view with a
visual indicator such as a CSS class or styling.
- Around line 270-275: The delete button for saved views in the MyTickets
component currently triggers deleteView immediately without any user
confirmation. Modify the onClick handler in the button element to prompt the
user for confirmation before executing the deleteView call. Wrap the
deleteView(view.name) invocation with a confirmation dialog that asks the user
to verify they want to delete the view, proceeding only if they confirm the
action.
In `@Frontend/src/user/pages/TicketDetail.jsx`:
- Around line 188-202: Replace plain text displays of dependency ticket IDs with
clickable navigation links to improve UX. In the blocked alert card (where
dependencyTicket is shown), convert the Ticket #{dependencyTicket} reference
into a clickable link that navigates to that ticket's detail page. Apply the
same treatment to all other locations where dependency ticket IDs are displayed
as plain text (field values and dependencies card). Use a consistent link
styling and ensure navigation occurs when users click these ticket ID
references.
In `@MobileApp/src/screens/user/TicketDetailScreen.js`:
- Around line 142-149: The TouchableOpacity component with the watch button
(styled with styles.watchBtn and onPress handler toggling isWatching) is missing
accessibility properties needed for screen reader compatibility. Add
accessibility properties to this component including accessibilityLabel to
provide a descriptive label of the button's purpose, accessibilityHint to
provide additional context about what the button does, accessibilityRole set to
"button" to identify it as an interactive element, and accessibilityState to
indicate the current state (watched or unwatched) so screen readers can properly
convey the button's state to users.
🪄 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: 59aad131-7835-4fa7-b51e-a44a9df4af4d
📒 Files selected for processing (12)
Frontend/src/admin/pages/AdminAnalytics.jsxFrontend/src/admin/pages/AdminDashboard.jsxFrontend/src/admin/pages/AdminSettings.jsxFrontend/src/admin/pages/AdminTicketDetail.jsxFrontend/src/pages/TicketDetailView.jsxFrontend/src/store/ticketStore.jsFrontend/src/user/pages/CreateTicket.jsxFrontend/src/user/pages/MyTickets.jsxFrontend/src/user/pages/TicketDetail.jsxMobileApp/src/screens/user/TicketDetailScreen.jsbackend/models/classifier/model.safetensorsbackend/models/ner/model.safetensors
💤 Files with no reviewable changes (2)
- backend/models/ner/model.safetensors
- backend/models/classifier/model.safetensors
| const heatmapData = []; | ||
|
|
||
| for (let day = 0; day < 7; day++) { | ||
| for (let hour = 0; hour < 24; hour++) { | ||
| heatmapData.push({ | ||
| day, | ||
| hour, | ||
| count: 0 | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| tickets.forEach(ticket => { | ||
| if (!ticket.created_at) return; | ||
|
|
||
| const date = new Date(ticket.created_at); | ||
| const day = date.getDay(); | ||
| const hour = date.getHours(); | ||
|
|
||
| const cell = heatmapData.find( | ||
| item => item.day === day && item.hour === hour | ||
| ); | ||
|
|
||
| if (cell) { | ||
| cell.count += 1; | ||
| } | ||
| }); | ||
|
|
||
| const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; | ||
|
|
||
| return { | ||
| total, open, resolved, highPriority, | ||
| volumeTimeline, categoryData, teamData, resolutionData, liveFeed | ||
| }; | ||
| total, | ||
| open, | ||
| resolved, | ||
| highPriority, | ||
| volumeTimeline, | ||
| categoryData, | ||
| teamData, | ||
| resolutionData, | ||
| liveFeed, | ||
| heatmapData | ||
| }; |
There was a problem hiding this comment.
Move days array outside useMemo to fix ReferenceError.
The days array is defined on line 152 inside the stats useMemo, but it's referenced on line 325 in the JSX render where it's not in scope. This will cause a runtime ReferenceError when the heatmap tries to display day names.
🐛 Proposed fix
+const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
const AdminAnalytics = () => {
const { profile } = useAuthStore();
const [tickets, setTickets] = useState([]);
const [loading, setLoading] = useState(true);
// ... fetchAnalytics ...
const stats = useMemo(() => {
// ... other stats ...
const heatmapData = [];
for (let day = 0; day < 7; day++) {
for (let hour = 0; hour < 24; hour++) {
heatmapData.push({
day,
hour,
count: 0
});
}
}
tickets.forEach(ticket => {
if (!ticket.created_at) return;
const date = new Date(ticket.created_at);
const day = date.getDay();
const hour = date.getHours();
const cell = heatmapData.find(
item => item.day === day && item.hour === hour
);
if (cell) {
cell.count += 1;
}
});
- const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return {
total,
open,
resolved,
highPriority,
volumeTimeline,
categoryData,
teamData,
resolutionData,
liveFeed,
heatmapData
};
}, [tickets]);
// ... render ...
- title={`${days[cell.day]} ${cell.hour}:00 → ${cell.count} tickets`}
+ title={`${DAYS[cell.day]} ${cell.hour}:00 → ${cell.count} tickets`}🤖 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 `@Frontend/src/admin/pages/AdminAnalytics.jsx` around lines 124 - 165, The days
array is currently defined inside the useMemo hook but needs to be accessible in
the JSX render section outside of that hook. Move the days array definition
(containing the day name strings 'Sun', 'Mon', 'Tue', etc.) outside and above
the useMemo hook where the heatmapData is being calculated. This will make it
available in the component's scope for use both within and outside the memoized
calculation, preventing the ReferenceError when the heatmap render tries to
access the day names.
| const date = new Date(ticket.created_at); | ||
| const day = date.getDay(); | ||
| const hour = date.getHours(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify how created_at timestamps are stored and whether consistent timezone display is expected
# Check if there are any timezone utilities or UTC conversion functions
rg -n -C3 'UTC|timezone|getUTC' --type=js --type=ts -g '!node_modules'
# Look for any timezone-related configuration or documentation
fd -e md -e txt README TIMEZONE TIME_ZONE -i --exec cat {}Repository: ritesh-1918/HELPDESK.AI
Length of output: 2826
🏁 Script executed:
# View the AdminAnalytics.jsx file around the problematic lines to understand context
sed -n '130,150p' Frontend/src/admin/pages/AdminAnalytics.jsx
# Check if formatTimelineDate is imported or used anywhere in AdminAnalytics.jsx
rg -n 'formatTimelineDate|dateUtils' Frontend/src/admin/pages/AdminAnalytics.jsx
# Look at the full heatmap processing logic to understand intent
sed -n '135,160p' Frontend/src/admin/pages/AdminAnalytics.jsxRepository: ritesh-1918/HELPDESK.AI
Length of output: 1205
🏁 Script executed:
# Check for any comments or logic around the heatmap that might explain why formatTimelineDate isn't used
sed -n '125,155p' Frontend/src/admin/pages/AdminAnalytics.jsx
# Verify the structure and purpose of heatmap vs timeline data
rg -n -B5 -A5 'heatmapData' Frontend/src/admin/pages/AdminAnalytics.jsx | head -40Repository: ritesh-1918/HELPDESK.AI
Length of output: 1708
Use formatTimelineDate() for heatmap date extraction to match timezone handling elsewhere in the component.
Lines 139-141 use direct new Date(ticket.created_at) calls, but the same file uses formatTimelineDate() for timeline data (line 120), which explicitly applies UTC forcing for timestamps without timezone markers. The inconsistency means the heatmap displays activity in the viewer's local timezone while the timeline uses UTC-adjusted times, resulting in misaligned data visualization within the same component. Replace with formatTimelineDate() or document the intentional difference.
🤖 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 `@Frontend/src/admin/pages/AdminAnalytics.jsx` around lines 139 - 141, Replace
the direct new Date(ticket.created_at) instantiation at lines 139-141 with a
call to formatTimelineDate(), which is already used at line 120 of the same
component for timeline data extraction. This ensures consistent timezone
handling throughout the component so that both the heatmap and timeline display
activity using the same timezone adjustment logic (UTC forcing for timestamps
without timezone markers). Extract the day and hour values from the result of
formatTimelineDate() instead of from the raw Date object.
| <div style={{ display: 'flex', alignItems: 'center', gap: '6px', padding: '4px 10px', background: '#f8faf9', borderRadius: '100px', border: '1px solid #e5e7eb' }}> | ||
| <Activity size={10} color="#9ca3af" /> | ||
| <span style={{ fontSize: '9px', fontWeight: 600, color: '#9ca3af', textTransform: 'uppercase', letterSpacing: '0.1em' }}> | ||
| Last Synced: {formatTimelineDate(new Date())} |
There was a problem hiding this comment.
"Last Synced" timestamp is misleading.
formatTimelineDate(new Date()) formats the current render time, not the actual data fetch time. This means "Last Synced" will constantly update with the current time instead of showing when the dashboard data was last refreshed. Track the fetch timestamp in state and display that instead.
🔧 Proposed fix
const AdminDashboard = () => {
const navigate = useNavigate();
const { profile } = useAuthStore();
const [tickets, setTickets] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(true);
const [categoryFilter, setCategoryFilter] = React.useState("all");
+ const [lastSyncTime, setLastSyncTime] = React.useState(null);
React.useEffect(() => {
if (profile) {
const fetchStats = async () => {
setIsLoading(true);
try {
// ... existing fetch logic ...
} else {
setTickets(data || []);
}
+ setLastSyncTime(new Date());
} catch (err) { console.error("Dashboard fetch error:", err); }
finally { setIsLoading(false); }
};
fetchStats();
const interval = setInterval(fetchStats, 30000);
return () => clearInterval(interval);
}
}, [profile]);
// ... rest of component ...
- Last Synced: {formatTimelineDate(new Date())}
+ Last Synced: {lastSyncTime ? formatTimelineDate(lastSyncTime) : 'Processing...'}📝 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.
| Last Synced: {formatTimelineDate(new Date())} | |
| const AdminDashboard = () => { | |
| const navigate = useNavigate(); | |
| const { profile } = useAuthStore(); | |
| const [tickets, setTickets] = React.useState([]); | |
| const [isLoading, setIsLoading] = React.useState(true); | |
| const [categoryFilter, setCategoryFilter] = React.useState("all"); | |
| const [lastSyncTime, setLastSyncTime] = React.useState(null); | |
| React.useEffect(() => { | |
| if (profile) { | |
| const fetchStats = async () => { | |
| setIsLoading(true); | |
| try { | |
| // ... existing fetch logic ... | |
| } else { | |
| setTickets(data || []); | |
| } | |
| setLastSyncTime(new Date()); | |
| } catch (err) { console.error("Dashboard fetch error:", err); } | |
| finally { setIsLoading(false); } | |
| }; | |
| fetchStats(); | |
| const interval = setInterval(fetchStats, 30000); | |
| return () => clearInterval(interval); | |
| } | |
| }, [profile]); | |
| // ... rest of component ... | |
| Last Synced: {lastSyncTime ? formatTimelineDate(lastSyncTime) : 'Processing...'} |
🤖 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 `@Frontend/src/admin/pages/AdminDashboard.jsx` at line 276, The issue is that
formatTimelineDate(new Date()) in the AdminDashboard component displays the
current render time rather than when the dashboard data was actually fetched,
causing the "Last Synced" timestamp to constantly update on every render. To fix
this, store the actual fetch timestamp in component state when the data is
retrieved (likely in a useEffect hook where your data fetching occurs), and then
pass that stored fetch timestamp to formatTimelineDate() instead of creating a
new Date object on each render.
| {/* 5. Accessibility Insights */} | ||
| <Card className="border-none shadow-2xl shadow-slate-200/40 rounded-[2rem] overflow-hidden bg-white"> | ||
| <div className="px-8 py-6 bg-slate-50 border-b border-slate-100 flex items-center justify-between"> | ||
| <h3 className="text-sm font-black text-slate-900 uppercase italic tracking-tight flex items-center gap-3"> | ||
| <Accessibility size={18} className="text-violet-500" /> | ||
| Accessibility Insights | ||
| </h3> | ||
| </div> | ||
|
|
||
| <CardContent className="p-8 space-y-6"> | ||
|
|
||
| <div className="bg-violet-50 border border-violet-100 rounded-2xl p-5"> | ||
| <h4 className="text-sm font-black text-violet-700 uppercase"> | ||
| Accessibility Score | ||
| </h4> | ||
|
|
||
| <div className="mt-3 flex items-center gap-4"> | ||
| <div className="text-4xl font-black text-violet-600"> | ||
| 92% | ||
| </div> | ||
|
|
||
| <div className="text-xs text-slate-500 font-bold uppercase tracking-widest"> | ||
| Excellent Accessibility | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="grid md:grid-cols-2 gap-4"> | ||
|
|
||
| <div className="border rounded-xl p-4"> | ||
| <h4 className="font-black text-xs uppercase"> | ||
| Keyboard Navigation | ||
| </h4> | ||
|
|
||
| <p className="text-xs text-emerald-600 mt-2"> | ||
| Enabled | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="border rounded-xl p-4"> | ||
| <h4 className="font-black text-xs uppercase"> | ||
| Responsive Design | ||
| </h4> | ||
|
|
||
| <p className="text-xs text-emerald-600 mt-2"> | ||
| Mobile Ready | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="border rounded-xl p-4"> | ||
| <h4 className="font-black text-xs uppercase"> | ||
| Readability | ||
| </h4> | ||
|
|
||
| <p className="text-xs text-amber-600 mt-2"> | ||
| Consider larger text options | ||
| </p> | ||
| </div> | ||
|
|
||
| <div className="border rounded-xl p-4"> | ||
| <h4 className="font-black text-xs uppercase"> | ||
| Screen Readers | ||
| </h4> | ||
|
|
||
| <p className="text-xs text-emerald-600 mt-2"> | ||
| Supported | ||
| </p> | ||
| </div> | ||
|
|
||
| </div> | ||
|
|
||
| <div className="bg-slate-50 rounded-xl p-4"> | ||
| <h4 className="text-xs font-black uppercase mb-2"> | ||
| Recommendations | ||
| </h4> | ||
|
|
||
| <ul className="text-xs text-slate-600 space-y-2"> | ||
| <li>• Enable dark mode for reduced eye strain.</li> | ||
| <li>• Use larger fonts for improved readability.</li> | ||
| <li>• Ensure keyboard-only navigation remains accessible.</li> | ||
| </ul> | ||
| </div> | ||
|
|
||
| </CardContent> | ||
| </Card> |
There was a problem hiding this comment.
Static accessibility data may mislead administrators.
The Accessibility Insights section displays a hardcoded "92%" score and fixed status values without performing actual accessibility measurements. This could give administrators false confidence about WCAG compliance and lead to missed accessibility issues that require attention.
Consider one of these approaches:
- Integrate an actual accessibility testing library (e.g., axe-core, pa11y) to provide real metrics
- Clearly label this section as "Example" or "Placeholder" until measurements are implemented
- Remove the section until real accessibility testing is available
The current implementation risks creating compliance gaps if administrators rely on these static indicators.
🤖 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 `@Frontend/src/admin/pages/AdminSettings.jsx` around lines 172 - 256, The
Accessibility Insights Card component in AdminSettings.jsx displays hardcoded
static values like "92%" and fixed status messages without performing actual
accessibility measurements, which could mislead administrators about real WCAG
compliance status. Address this by either: (1) integrating an actual
accessibility testing library such as axe-core or pa11y to dynamically calculate
and display real accessibility metrics and recommendations instead of the
hardcoded "Accessibility Score" percentage and status values, (2) adding a clear
visual indicator or banner labeling the entire section as "Example" or
"Placeholder" to make it clear these are not real measurements, or (3) removing
the entire Card section until real accessibility testing capabilities are
available. Choose the approach that best fits your project roadmap and implement
accordingly to ensure administrators are not misled about actual accessibility
compliance.
| const handleSendReminder = async () => { | ||
| try { | ||
| setReminderSent(true); | ||
|
|
||
| showToast( | ||
| "Follow-up reminder sent successfully.", | ||
| "success" | ||
| ); | ||
|
|
||
| setTicket(prev => ({ | ||
| ...prev, | ||
| metadata: { | ||
| ...prev.metadata, | ||
| last_reminder_sent: new Date().toISOString() | ||
| } | ||
| })); | ||
| } catch (err) { | ||
| showToast( | ||
| "Failed to send reminder.", | ||
| "error" | ||
| ); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Reminder is not actually sent or persisted.
The handleSendReminder function updates only local React state and displays a success toast, but it never makes an API call to send the reminder or persist the last_reminder_sent metadata to the database. The reminderSent flag and metadata update will be lost on page refresh. Either add a database update to persist the reminder timestamp, or implement actual reminder sending logic (e.g., email notification, in-app notification).
🔧 Proposed fix (persist to database)
const handleSendReminder = async () => {
try {
+ // Persist reminder metadata to database
+ const { error: updateError } = await supabase
+ .from('tickets')
+ .update({
+ metadata: {
+ ...ticket.metadata,
+ last_reminder_sent: new Date().toISOString()
+ }
+ })
+ .eq('id', ticket_id);
+
+ if (updateError) throw updateError;
+
setReminderSent(true);
showToast(
"Follow-up reminder sent successfully.",
"success"
);
setTicket(prev => ({
...prev,
metadata: {
...prev.metadata,
last_reminder_sent: new Date().toISOString()
}
}));
} catch (err) {
showToast(
- "Failed to send reminder.",
+ "Failed to send reminder: " + err.message,
"error"
);
}
};Note: This persists the timestamp but doesn't actually send a notification. If the feature should send emails or other notifications, implement that logic as well.
🤖 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 `@Frontend/src/admin/pages/AdminTicketDetail.jsx` around lines 181 - 203, The
handleSendReminder function updates only local React state without making any
API call to actually send the reminder or persist the last_reminder_sent
metadata to the database. This means the reminder data will be lost on page
refresh. Add an API call (e.g., using fetch or your HTTP client) before the
success toast in handleSendReminder to persist the updated metadata with the
last_reminder_sent timestamp to the database. Wrap the state update and toast in
a .then() block after the API call succeeds, and ensure errors from the API call
are caught in the catch block.
| localStorage.setItem( | ||
| 'savedTicketViews', | ||
| JSON.stringify(updatedViews) | ||
| ); |
There was a problem hiding this comment.
Wrap all localStorage write operations in error handling. Both saveCurrentView and deleteView call localStorage.setItem without catching QuotaExceededError or other storage exceptions; the shared root cause is missing resilience around browser storage APIs.
Frontend/src/user/pages/MyTickets.jsx#L148-L151: wrap thesetItemcall insaveCurrentViewin try-catch, revert state on failure, and show user feedback (e.g., "Unable to save view. Storage quota may be exceeded.").Frontend/src/user/pages/MyTickets.jsx#L174-L177: wrap thesetItemcall indeleteViewin try-catch, revertsavedViewsstate on failure, and notify the user.
📍 Affects 1 file
Frontend/src/user/pages/MyTickets.jsx#L148-L151(this comment)Frontend/src/user/pages/MyTickets.jsx#L174-L177
🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 148 - 151, Add error
handling to both localStorage write operations in MyTickets.jsx. In the
saveCurrentView function at lines 148-151, wrap the localStorage.setItem call in
a try-catch block that catches QuotaExceededError and other storage exceptions;
on failure, revert the updatedViews state change and display an error message to
the user such as "Unable to save view. Storage quota may be exceeded." In the
deleteView function at lines 174-177, similarly wrap the localStorage.setItem
call in a try-catch block; on failure, revert the savedViews state change and
notify the user of the failure. Both sites must restore their respective state
objects to their previous values before the failed write attempt and provide
user-facing feedback.
| const deleteView = (name) => { | ||
| const updatedViews = | ||
| savedViews.filter(v => v.name !== name); | ||
|
|
||
| setSavedViews(updatedViews); | ||
|
|
||
| localStorage.setItem( | ||
| 'savedTicketViews', | ||
| JSON.stringify(updatedViews) | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Handle localStorage errors in deleteView.
Similar to saveCurrentView, localStorage.setItem can throw but is not wrapped in error handling.
🛡️ Add error handling
const deleteView = (name) => {
const updatedViews =
savedViews.filter(v => v.name !== name);
setSavedViews(updatedViews);
- localStorage.setItem(
- 'savedTicketViews',
- JSON.stringify(updatedViews)
- );
+ try {
+ localStorage.setItem(
+ 'savedTicketViews',
+ JSON.stringify(updatedViews)
+ );
+ } catch (error) {
+ console.error('Failed to delete view:', error);
+ // Revert state
+ setSavedViews(savedViews);
+ setError('Unable to delete view.');
+ }
};📝 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.
| const deleteView = (name) => { | |
| const updatedViews = | |
| savedViews.filter(v => v.name !== name); | |
| setSavedViews(updatedViews); | |
| localStorage.setItem( | |
| 'savedTicketViews', | |
| JSON.stringify(updatedViews) | |
| ); | |
| }; | |
| const deleteView = (name) => { | |
| const updatedViews = | |
| savedViews.filter(v => v.name !== name); | |
| setSavedViews(updatedViews); | |
| try { | |
| localStorage.setItem( | |
| 'savedTicketViews', | |
| JSON.stringify(updatedViews) | |
| ); | |
| } catch (error) { | |
| console.error('Failed to delete view:', error); | |
| // Revert state | |
| setSavedViews(savedViews); | |
| setError('Unable to delete view.'); | |
| } | |
| }; |
🤖 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 `@Frontend/src/user/pages/MyTickets.jsx` around lines 168 - 178, The deleteView
function's localStorage.setItem call lacks error handling and can throw
exceptions. Wrap the localStorage.setItem operation inside a try-catch block to
handle any potential errors (similar to how it should be handled in
saveCurrentView). In the catch block, log the error appropriately so failures to
persist view deletions to localStorage are properly captured and don't cause the
application to crash.
| <div> | ||
| <p className="text-xs uppercase font-bold text-gray-400 tracking-wider mb-1.5"> | ||
| Blocked By | ||
| </p> | ||
| <p className="text-sm font-semibold text-gray-900 bg-gray-50 border border-gray-100 rounded-lg px-3 py-2"> | ||
| {dependencyTicket ? `#${dependencyTicket}` : 'None'} | ||
| </p> | ||
| </div> | ||
|
|
||
| <div> | ||
| <p className="text-xs uppercase font-bold text-gray-400 tracking-wider mb-1.5"> | ||
| Related Ticket | ||
| </p> | ||
| <p className="text-sm font-semibold text-gray-900 bg-gray-50 border border-gray-100 rounded-lg px-3 py-2"> | ||
| {relatedTicket ? `#${relatedTicket}` : 'None'} | ||
| </p> | ||
| </div> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Verify placement of dependency fields.
The "Blocked By" and "Related Ticket" field divs (lines 218-234) appear to be placed outside any Card container. The "Ticket Details" Card opens at line 235, which means these fields are floating in the left column without proper containment.
If these fields are intended to be part of the Ticket Details card, they should be moved inside it. If they're meant to be standalone, consider wrapping them in a Card for visual consistency.
💡 Suggested fix: Move fields inside Ticket Details card
Move lines 218-234 to appear after the existing fields inside the Ticket Details card grid (after line 263):
<div>
<p className="text-xs uppercase font-bold text-gray-400 tracking-wider mb-1.5">Assigned Team</p>
<p className="text-sm font-semibold text-gray-900 bg-gray-50 border border-gray-100 rounded-lg px-3 py-2">
{ticket.assigned_team || 'General Support'}
</p>
</div>
+ <div>
+ <p className="text-xs uppercase font-bold text-gray-400 tracking-wider mb-1.5">
+ Blocked By
+ </p>
+ <p className="text-sm font-semibold text-gray-900 bg-gray-50 border border-gray-100 rounded-lg px-3 py-2">
+ {dependencyTicket ? `#${dependencyTicket}` : 'None'}
+ </p>
+ </div>
+ <div>
+ <p className="text-xs uppercase font-bold text-gray-400 tracking-wider mb-1.5">
+ Related Ticket
+ </p>
+ <p className="text-sm font-semibold text-gray-900 bg-gray-50 border border-gray-100 rounded-lg px-3 py-2">
+ {relatedTicket ? `#${relatedTicket}` : 'None'}
+ </p>
+ </div>
</div>
</Card>🤖 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 `@Frontend/src/user/pages/TicketDetail.jsx` around lines 218 - 234, The
"Blocked By" and "Related Ticket" field divs are currently positioned outside of
the Ticket Details Card container, making them float unstyled in the layout.
Move these two div blocks that contain the dependency fields (the ones
displaying dependencyTicket and relatedTicket values) from their current
location to inside the Ticket Details Card container, positioning them after the
existing fields within the card's grid layout to maintain visual consistency and
proper containment.
| const [newMessage, setNewMessage] = useState(''); | ||
| const [loading, setLoading] = useState(true); | ||
| const [ticket, setTicket] = useState(null); | ||
| const [isWatching, setIsWatching] = useState(false); |
There was a problem hiding this comment.
Critical: Watch state is never initialized from the backend.
The isWatching state is hardcoded to false and never fetched from the server. When the component mounts, it should query the backend to check if the current user is already watching this ticket. Without this, the button will always show "Watch" even if the user has previously watched the ticket.
🔍 Proposed fix to initialize watch state from backend
Add a fetch call in the useEffect to load the watch status:
const [ticket, setTicket] = useState(null);
- const [isWatching, setIsWatching] = useState(false);
+ const [isWatching, setIsWatching] = useState(false);
const navigation = useNavigation();
const flatListRef = useRef(null);
useEffect(() => {
fetchTicketDetails();
fetchMessages();
+ fetchWatchStatus();
// Set up real-time subscription for messagesAnd add the fetch function:
+ const fetchWatchStatus = async () => {
+ try {
+ const { data: { user } } = await supabase.auth.getUser();
+ if (!user) return;
+
+ const { data, error } = await supabase
+ .from('ticket_watchers')
+ .select('id')
+ .eq('ticket_id', ticketId)
+ .eq('user_id', user.id)
+ .single();
+
+ if (!error && data) {
+ setIsWatching(true);
+ }
+ } catch (error) {
+ console.error('Error fetching watch status:', error);
+ }
+ };
+
const fetchTicketDetails = async () => {🤖 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 `@MobileApp/src/screens/user/TicketDetailScreen.js` at line 25, The isWatching
state is initialized to false but never updates from the backend data. Add a
useEffect hook in the TicketDetailScreen component that runs on mount and
fetches the watch status from the backend for the current ticket and user. Parse
the response and call setIsWatching with the actual watch status retrieved from
the server, ensuring the button displays the correct state based on whether the
user is already watching this ticket.
| <TouchableOpacity | ||
| style={styles.watchBtn} | ||
| onPress={() => setIsWatching(!isWatching)} | ||
| > | ||
| <Text style={styles.watchBtnText}> | ||
| {isWatching ? "Watching" : "Watch"} | ||
| </Text> | ||
| </TouchableOpacity> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check for ticket_watchers table or watch-related schema
# Search for watch-related database schema or migrations
rg -n --type-add 'sql:*.sql' --type sql -i 'ticket_watch|watch.*ticket'
# Search for watch-related Supabase queries in the codebase
rg -n -C3 'ticket_watchers|ticket_watch'
# Check for any existing watch API endpoints
rg -n -C3 "'/api.*watch|watch.*endpoint'"Repository: ritesh-1918/HELPDESK.AI
Length of output: 49
🏁 Script executed:
# Check repository structure and locate backend
git ls-files | head -30Repository: ritesh-1918/HELPDESK.AI
Length of output: 825
🏁 Script executed:
# Check if Supabase is set up and how it's used in the app
rg -n "supabase" MobileApp/src/screens/user/TicketDetailScreen.js
# Look for any backend directory
fd -t d "backend|server|api" --max-depth 2Repository: ritesh-1918/HELPDESK.AI
Length of output: 518
🏁 Script executed:
# Examine the actual TicketDetailScreen.js file to understand the implementation
wc -l MobileApp/src/screens/user/TicketDetailScreen.jsRepository: ritesh-1918/HELPDESK.AI
Length of output: 120
🏁 Script executed:
# Check the backend directory structure
fd -t f "." backend/ --max-depth 2 | head -30Repository: ritesh-1918/HELPDESK.AI
Length of output: 1014
🏁 Script executed:
# Look at the specific lines in TicketDetailScreen.js around the watch button
sed -n '130,160p' MobileApp/src/screens/user/TicketDetailScreen.jsRepository: ritesh-1918/HELPDESK.AI
Length of output: 1178
🏁 Script executed:
# Search for setIsWatching to see if there's any backend call
rg -n "setIsWatching|isWatching" MobileApp/src/screens/user/TicketDetailScreen.jsRepository: ritesh-1918/HELPDESK.AI
Length of output: 218
🏁 Script executed:
# Check for any watch-related code in backend (with different possible naming)
rg -i "watch" backend/ --max-count 20Repository: ritesh-1918/HELPDESK.AI
Length of output: 718
🏁 Script executed:
# Check the complete useEffect hooks and functions in TicketDetailScreen.js to see if watch state is persisted
sed -n '1,100p' MobileApp/src/screens/user/TicketDetailScreen.jsRepository: ritesh-1918/HELPDESK.AI
Length of output: 2886
🏁 Script executed:
# Check Supabase schema/tables setup - look for profiles, tickets tables to understand schema
rg -n "\.from\(" MobileApp/src/screens/user/TicketDetailScreen.js | head -20Repository: ritesh-1918/HELPDESK.AI
Length of output: 280
🏁 Script executed:
# Search for any database schema or migrations that define tables
find . -name "*.sql" -type f | xargs cat 2>/dev/null | head -100Repository: ritesh-1918/HELPDESK.AI
Length of output: 3855
🏁 Script executed:
# Check if there's any watch-related functionality in the entire MobileApp codebase
rg -r "ticket_watchers|watchers" MobileApp/ 2>/dev/nullRepository: ritesh-1918/HELPDESK.AI
Length of output: 1233
Critical: Watch toggle lacks backend persistence and infrastructure.
The watch button only toggles local state without persisting changes to the backend:
- Watch state is lost on app restart or navigation away
- No
ticket_watcherstable exists in the database schema - No watch API endpoints or backend integration
- Users will never receive notifications from watching a ticket
This contradicts the PR objective of users receiving "notifications on status changes, comments, and assignment updates" when watching. Implementing this feature requires:
- Adding a
ticket_watcherstable to track which users watch which tickets - Creating backend API endpoints for adding/removing watchers
- Integrating with the notification system to send alerts to watchers
🔧 Proposed implementation
Create the database table:
CREATE TABLE ticket_watchers (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
ticket_id UUID NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
UNIQUE(ticket_id, user_id)
);Update the component:
- <TouchableOpacity
- style={styles.watchBtn}
- onPress={() => setIsWatching(!isWatching)}
- >
+ <TouchableOpacity
+ style={styles.watchBtn}
+ onPress={handleWatchToggle}
+ >
<Text style={styles.watchBtnText}>
{isWatching ? "Watching" : "Watch"}
</Text>
</TouchableOpacity>Add the handler:
+ const handleWatchToggle = async () => {
+ try {
+ const { data: { user } } = await supabase.auth.getUser();
+ if (!user) {
+ console.error('User not authenticated');
+ return;
+ }
+
+ if (isWatching) {
+ const { error } = await supabase
+ .from('ticket_watchers')
+ .delete()
+ .eq('ticket_id', ticketId)
+ .eq('user_id', user.id);
+ if (error) throw error;
+ setIsWatching(false);
+ } else {
+ const { error } = await supabase
+ .from('ticket_watchers')
+ .insert({ ticket_id: ticketId, user_id: user.id });
+ if (error) throw error;
+ setIsWatching(true);
+ }
+ } catch (error) {
+ console.error('Error toggling watch status:', error);
+ }
+ };
+
return (🤖 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 `@MobileApp/src/screens/user/TicketDetailScreen.js` around lines 142 - 149, The
watch button toggle in the TouchableOpacity component with the onPress handler
only updates local state and does not persist the watch action to the backend.
To fix this, you need to: (1) create a `ticket_watchers` table in the database
to store which users are watching which tickets, including a unique constraint
on ticket_id and user_id; (2) create backend API endpoints for adding and
removing watchers that handle the user-ticket watch relationship; (3) replace
the onPress handler that currently calls setIsWatching(!isWatching) with an
actual API call to add or remove the current user as a watcher based on the
current isWatching state; (4) integrate the watch functionality with your
notification system so that users watching a ticket receive notifications when
the ticket status changes, comments are added, or assignments are updated.
|
Hi @jainiksha! Please redirect your PR target branch to |
Description
Added a Ticket Watchlist feature to the Ticket Details screen. Users can now watch or unwatch tickets directly from the ticket page, making it easier to keep track of important issues without being assigned to them.
Changes Made
Fixes #2857
Summary by CodeRabbit
Release Notes