Skip to content

Corrected filter bars and tenant page routing and apis - #134

Merged
Nihalpuse merged 2 commits into
mainfrom
punya
Jan 3, 2026
Merged

Corrected filter bars and tenant page routing and apis#134
Nihalpuse merged 2 commits into
mainfrom
punya

Conversation

@PunyaChadha

@PunyaChadha PunyaChadha commented Dec 30, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced dashboard filters with saved filter support and clear functionality across all sections.
    • Added new filter categories: bedrooms, bathrooms, days listed, syndication status, and marketing status.
    • Implemented tenant document management with upload, download, and deletion capabilities.
    • Added tenant applications and requests sections with improved data visibility.
    • Introduced tenant deletion with confirmation and error handling.
  • Improvements

    • Enhanced loading states and error messaging throughout dashboard.
    • Improved filter synchronization and placeholder handling for empty filter options.
    • Better empty state differentiation based on search and filter criteria.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 30, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR enhances dashboard filtering capabilities across multiple features by introducing new filter state props and implementing saved filter management. Additionally, the Tenants module undergoes substantial refactoring to replace mock data with backend-driven queries, data transformations, and CRUD operations via React Query hooks.

Changes

Cohort / File(s) Summary
Core Filter Component
src/pages/Dashboard/components/DashboardFilter.tsx
Added public props onClearSavedFilter, activeSavedFilter, initialFilters, and showClearAll; wired synchronization of filter state; introduced saved filter badge with clear button; enhanced rendering to handle disabled items (__no_items__).
Dashboard Features — Filter Integration
src/pages/Dashboard/features/Application/Application.tsx, src/pages/Dashboard/features/Equipments/Equipments.tsx, src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx, src/pages/Dashboard/features/Leads/leads.tsx, src/pages/Dashboard/features/Leases/Leases.tsx, src/pages/Dashboard/features/Listing/Listing.tsx, src/pages/Dashboard/features/Units/Units.tsx
Integrated new DashboardFilter props (initialFilters, showClearAll); refactored filter state to generic Record format; extended data transformation with new fields (propertyId, unitId, backendStatus, etc.); implemented filtering logic with placeholder handling; added computed filter options.
Tasks — Saved Filter State
src/pages/Dashboard/features/Tasks/Tasks.tsx
Added activeSavedFilter state tracking; implemented handleClearSavedFilter callback; passed new props (onClearSavedFilter, activeSavedFilter) to DashboardFilter; updated savedFilters initialization.
Tenants Module — Data Fetching & Detail View
src/pages/Dashboard/features/Tenants/TenantDetail.tsx
Integrated useGetTenant and useDeleteTenant hooks; replaced mock data with transformTenantForDetail transformation function; added loading/error states with Loader2; expanded section component props (tenantId/tenantUserId); wired delete flow with confirmation and navigation.
Tenants Module — List & Management
src/pages/Dashboard/features/Tenants/Tenants.tsx
Integrated useGetAllTenants and useDeleteTenant; replaced mock data with React Query; added client-side search, filter, and pagination; implemented derived data pipelines (transform → filter → sort → paginate); updated TenantCard with explicit props and delete handling.
Tenants Module — Section Components
src/pages/Dashboard/features/Tenants/components/TenantProfileSection.tsx, src/pages/Dashboard/features/Tenants/components/TenantLeasesSection.tsx, src/pages/Dashboard/features/Tenants/components/TenantApplicationsSection.tsx, src/pages/Dashboard/features/Tenants/components/TenantInsuranceSection.tsx, src/pages/Dashboard/features/Tenants/components/TenantRequestsSection.tsx, src/pages/Dashboard/features/Tenants/components/TenantTransactionsSection.tsx
Added typed props (TenantXxxSectionProps with tenantId); integrated data-fetching hooks (documents, leases, applications); implemented transformations and empty states; wired document upload/delete with validation and error handling; updated section signatures to accept tenantId and other context data.
Units — Enhancements
src/pages/Dashboard/features/Units/Units.tsx, src/pages/Dashboard/features/Units/components/UnitGroupCard.tsx, src/pages/Dashboard/features/Units/components/UnitItem.tsx
Replaced activeListingsMap with consolidated listingsMap (active/draft at property/unit levels); added statusMap for backend-to-frontend mapping; computed totalMonthlyRent and currency-aware balanceCategory; extended filtering to include status, occupancy, propertyType, marketingStatus, and balance; added propertyStatus and hasDraftListing to Unit/UnitGroup interfaces.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Carrots sorted, tunnels mapped with care,
Filters organized everywhere!
From Tasks to Leases, Units aligned,
Real tenant data, no more confined.
Our warren's dashboard grows so bright, 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and generic, using terms like 'corrected' without specifying what was corrected. It mentions three areas (filter bars, tenant page routing, APIs) but doesn't clearly convey the primary changes or scope. Consider a more specific title that highlights the main change, such as 'Add filter synchronization and tenant detail page backend integration' or 'Implement dashboard filter state persistence and tenant API integration'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/pages/Dashboard/features/Leases/Leases.tsx (1)

303-338: Second table section bypasses filtering and pagination.

Lines 306-337 render a static slice of MOCK_LEASES.slice(2, 4) which ignores the search, filter, and pagination logic applied to the first table. This creates inconsistent behavior where the second "Grove Street" group always shows the same items regardless of filters.

🔎 Consider applying consistent filtering

Either:

  1. Remove the hardcoded second table section
  2. Group leases by property and apply filters to all groups
  3. Add a comment explaining this is intentional demo/placeholder UI
🧹 Nitpick comments (22)
src/pages/Dashboard/features/Equipments/Equipments.tsx (1)

256-278: Consider extracting placeholder filter logic.

The pattern of ignoring __no_items__ placeholder values is repeated across multiple filter checks. Consider extracting a helper function to reduce duplication.

🔎 Suggested helper function
// Helper to check if filter should match (ignoring placeholder values)
const hasValidFilterValues = (filterValues: string[] | undefined): string[] => {
    return (filterValues || []).filter(v => v !== '__no_items__');
};

// Usage in filtering:
const validCategories = hasValidFilterValues(filters.category);
const matchesCategory = validCategories.length === 0 || validCategories.includes(item.category);
src/pages/Dashboard/components/DashboardFilter.tsx (1)

73-80: Potential unnecessary re-renders due to filterOptions in dependency array.

The filterOptions object reference may change on every parent render (if not memoized), causing this effect to run unnecessarily. Consider using a more stable comparison or memoizing filterOptions in parent components.

🔎 Alternative approach using JSON comparison
 useEffect(() => {
     const updated: Record<string, string[]> = {};
     Object.keys(filterOptions).forEach(key => {
         updated[key] = initialFilters[key] || [];
     });
-    setSelectedFilters(updated);
-}, [initialFilters, filterOptions]);
+    setSelectedFilters(prev => {
+        // Only update if actually different
+        const prevKeys = Object.keys(prev).sort().join(',');
+        const newKeys = Object.keys(updated).sort().join(',');
+        if (prevKeys !== newKeys) return updated;
+        
+        const hasChanges = Object.keys(updated).some(
+            key => JSON.stringify(prev[key]) !== JSON.stringify(updated[key])
+        );
+        return hasChanges ? updated : prev;
+    });
+}, [initialFilters, filterOptions]);

Alternatively, ensure parent components memoize filterOptions with useMemo.

src/pages/Dashboard/features/Tenants/Tenants.tsx (2)

38-48: Placeholder filter values will be selectable but have no effect.

Using __no_items__ as a placeholder value creates a selectable option that does nothing. Consider disabling these filter categories entirely in the DashboardFilter component when no real options are available, or use a disabled property on the options.

🔎 Consider marking options as disabled

If DashboardFilter supports a disabled property on options:

 const filterOptions: Record<string, FilterOption[]> = {
     tenantType: [
-        { value: '__no_items__', label: 'No tenant types available' }
+        { value: '__no_items__', label: 'No tenant types available', disabled: true }
     ],
     ...
 };

Alternatively, conditionally exclude empty filter categories from filterOptions.


107-116: Async delete handler invoked without awaiting in JSX.

At line 203, handleDeleteTenant(tenant.id).catch(console.error) is called, but this pattern can lead to unhandled promise issues in some React versions. The handler itself is well-structured with confirmation and error handling.

🔎 Consider wrapping in a non-async callback
 onDelete={() => {
-    handleDeleteTenant(tenant.id).catch(console.error);
+    void handleDeleteTenant(tenant.id);
 }}

The void operator explicitly marks the promise as intentionally not awaited.

src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx (1)

37-41: Added keyType, propertyId, and unitId to transformation.

The keyType field preserves the backend value for filtering, which is correct. However, propertyId and unitId are added but not currently used in any filter or display logic. If not needed, consider removing them to avoid confusion.

src/pages/Dashboard/features/Leases/Leases.tsx (1)

7-49: Mock data in production code should be replaced.

The MOCK_LEASES constant appears to be placeholder data. Consider migrating to React Query hooks (similar to Tenants.tsx) for backend integration.

src/pages/Dashboard/features/Listing/Listing.tsx (2)

186-196: Duplicated daysListed calculation logic.

The same date calculation appears twice: once for MULTI property units (lines 186-193) and once for SINGLE properties (lines 269-276). Consider extracting to a helper function.

🔎 Extract helper function
const calculateDaysListed = (listedAt: string | undefined): number | undefined => {
    if (!listedAt) return undefined;
    const listedDate = new Date(listedAt);
    const today = new Date();
    const diffTime = today.getTime() - listedDate.getTime();
    return Math.floor(diffTime / (1000 * 60 * 60 * 24));
};

Also applies to: 269-279


442-442: Type assertion as any bypasses type safety.

The onFiltersChange={(newFilters) => setFilters(newFilters as any)} cast indicates a type mismatch between DashboardFilter's output and the component's specific filter state type.

🔎 Use `Record` for filters state

Change the filters state type to match DashboardFilter's output:

-const [filters, setFilters] = useState<{
-    status: string[];
-    daysListed: string[];
-    syndication: string[];
-    bedrooms: string[];
-    bathrooms: string[];
-}>({...});
+const [filters, setFilters] = useState<Record<string, string[]>>({
+    status: [],
+    daysListed: [],
+    syndication: [],
+    bedrooms: [],
+    bathrooms: []
+});

Then update onFiltersChange:

-onFiltersChange={(newFilters) => setFilters(newFilters as any)}
+onFiltersChange={setFilters}
src/pages/Dashboard/features/Units/Units.tsx (1)

255-322: Balance category calculation is complex and may have misleading naming.

The "balance" filter actually categorizes by total monthly rent, not account balance. Consider renaming to rentCategory or similar for clarity.

Additionally, the large countryToCurrency and currencyThresholds maps could be extracted to a shared utility.

🔎 Consider renaming and extracting utilities
  1. Rename for clarity:
-balanceCategory: 'low' | 'medium' | 'high' = 'medium';
+rentCategory: 'low' | 'medium' | 'high' = 'medium';
  1. Extract currency mapping to a shared utility file for reuse across components.
src/pages/Dashboard/features/Tenants/components/TenantInsuranceSection.tsx (1)

7-8: Unused tenantId parameter.

The tenantId prop is accepted but not used. This is acceptable for a placeholder component, but consider adding an ESLint disable comment or prefixing with underscore (_tenantId) to signal intentional non-use.

🔎 Suggested fix
-const TenantInsuranceSection: React.FC<TenantInsuranceSectionProps> = ({ tenantId }) => {
+const TenantInsuranceSection: React.FC<TenantInsuranceSectionProps> = ({ tenantId: _tenantId }) => {
src/pages/Dashboard/features/Tenants/components/TenantTransactionsSection.tsx (1)

23-26: Unused props and misleading empty state.

Both tenantId and tenant props are accepted but unused. Additionally, the empty state check on line 94 is unreachable because transactions is hard-coded with data. Consider either using an empty array to test the empty state, or adding a comment clarifying this is intentional mock data.

🔎 Suggested fix to make empty state testable
-const TenantTransactionsSection = ({ tenantId, tenant }: TenantTransactionsSectionProps) => {
+const TenantTransactionsSection = ({ tenantId: _tenantId, tenant: _tenant }: TenantTransactionsSectionProps) => {
     // Note: There's no direct API for tenant transactions yet
     // This is a placeholder that shows empty state
-    const transactions: Transaction[] = [
-        {
-            id: 1,
-            status: 'Paid',
-            ...
-        },
-        ...
-    ];
+    // TODO: Replace with actual API call using tenantId
+    const transactions: Transaction[] = [];
src/pages/Dashboard/features/Tenants/components/TenantApplicationsSection.tsx (2)

55-63: Move statusMap outside the render loop.

statusMap is a static object but is recreated for each application in the .map() callback. Move it outside the component for better performance.

🔎 Proposed fix
+const STATUS_MAP: Record<string, string> = {
+    'APPROVED': 'Approved',
+    'SUBMITTED': 'Pending',
+    'UNDER_REVIEW': 'Pending',
+    'DRAFT': 'Draft',
+    'REJECTED': 'Rejected',
+    'WITHDRAWN': 'Withdrawn',
+};
+
 const TenantApplicationsSection = ({ tenantId, tenantUserId }: TenantApplicationsSectionProps) => {
     // ... inside map callback:
-                const statusMap: Record<string, string> = {
-                    'APPROVED': 'Approved',
-                    'SUBMITTED': 'Pending',
-                    'UNDER_REVIEW': 'Pending',
-                    'DRAFT': 'Draft',
-                    'REJECTED': 'Rejected',
-                    'WITHDRAWN': 'Withdrawn',
-                };
-                const status = statusMap[app.status] || 'Pending';
+                const status = STATUS_MAP[app.status] || 'Pending';

6-8: Unused tenantId prop.

The tenantId prop is defined but only tenantUserId is used for filtering. Consider removing tenantId from the interface if not needed, or prefix with underscore to indicate intentional non-use.

src/pages/Dashboard/features/Tenants/components/TenantRequestsSection.tsx (2)

17-20: Unused tenantId prop.

The tenantId prop is accepted but not used. Consider prefixing with underscore (_tenantId) to indicate intentional non-use until API integration is complete.


98-104: Empty click handler on View button.

The View button has an empty onClick handler. Consider either implementing navigation to the request detail or removing the button until the functionality is ready.

🔎 Proposed fix
                         <button
                             className="bg-[#3A6D6C] text-white px-6 py-2 rounded-full text-xs font-medium hover:bg-[#2c5251] transition-colors shadow-[inset_0_4px_2px_rgba(0,0,0,0.1)] flex items-center gap-2"
-                            onClick={() => { }}
+                            onClick={() => console.log('TODO: Navigate to request detail', request.id)}
                         >
                             <Eye className="w-4 h-4" />
                             View
                         </button>
src/pages/Dashboard/features/Tenants/components/TenantLeasesSection.tsx (1)

19-32: Use React Query instead of direct service calls for consistency.

This component uses useState + useEffect for data fetching while TenantApplicationsSection uses React Query (useGetAllApplications). Consider using a similar React Query hook for consistency, caching benefits, and better error/loading state management.

🔎 Example refactor
-import { useMemo, useEffect, useState } from 'react';
-import { leasingService, type BackendLeasing } from '../../../../../services/leasing.service';
+import { useMemo } from 'react';
+import { useGetAllLeasings } from '../../../../../hooks/useLeasingQueries';
+import type { BackendLeasing } from '../../../../../services/leasing.service';

 const TenantLeasesSection = ({ tenantId, tenant }: TenantLeasesSectionProps) => {
     const navigate = useNavigate();
-    const [allLeases, setAllLeases] = useState<BackendLeasing[]>([]);
-    const [isLoading, setIsLoading] = useState(true);
-    
-    useEffect(() => {
-        const fetchLeases = async () => {
-            try {
-                setIsLoading(true);
-                const leases = await leasingService.getAll();
-                setAllLeases(leases);
-            } catch (error) {
-                console.error('Failed to fetch leases:', error);
-            } finally {
-                setIsLoading(false);
-            }
-        };
-        fetchLeases();
-    }, []);
+    const { data: allLeases = [], isLoading, error } = useGetAllLeasings();
src/pages/Dashboard/features/Tenants/components/TenantProfileSection.tsx (4)

73-87: File extension validation not enforced.

While MIME type is validated, the file extension is not checked. MIME types can be spoofed. Consider adding extension validation for defense in depth.

🔎 Proposed fix
     const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
         const file = e.target.files?.[0];
         if (file) {
             // Validate file type
             if (!allowedFileTypes.includes(file.type)) {
                 alert(`Invalid file type. Please select one of the following: ${allowedFileExtensions.join(', ')}`);
                 if (fileInputRef.current) {
                     fileInputRef.current.value = '';
                 }
                 setSelectedFile(null);
                 return;
             }
+            // Also validate file extension
+            const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
+            if (!allowedFileExtensions.includes(fileExtension)) {
+                alert(`Invalid file extension. Please select one of the following: ${allowedFileExtensions.join(', ')}`);
+                if (fileInputRef.current) {
+                    fileInputRef.current.value = '';
+                }
+                setSelectedFile(null);
+                return;
+            }
             setSelectedFile(file);
         }
     };

129-135: Duplicate formatDate utility.

A formatDate utility already exists at src/utils/formatDate.ts. Consider reusing it for consistency, or if the format differs, consider extending the existing utility.


164-164: Avoid using any type.

The loop variables (contact, pet, vehicle) are typed as any. The types are already defined in TenantProfileSectionProps interface - use those instead.

🔎 Proposed fix
-                {tenant.emergencyContacts.map((contact: any, index: number) => (
+                {tenant.emergencyContacts.map((contact, index) => (

TypeScript will infer the correct type from tenant.emergencyContacts which is already typed in the interface.


118-127: Add loading state during document deletion.

The delete operation doesn't show a loading indicator while the mutation is in progress. Users might click multiple times or be confused about the state.

🔎 Proposed fix
+    const [deletingDocId, setDeletingDocId] = useState<string | null>(null);
+
     const handleDeleteDocument = async (documentId: string) => {
         if (window.confirm('Are you sure you want to delete this document?')) {
             try {
+                setDeletingDocId(documentId);
                 await deleteDocumentMutation.mutateAsync(documentId);
             } catch (error) {
                 console.error('Failed to delete document:', error);
                 alert(`Failed to delete document: ${error instanceof Error ? error.message : 'Unknown error'}`);
+            } finally {
+                setDeletingDocId(null);
             }
         }
     };

// In the delete button JSX:
-                                    <button
-                                        onClick={() => handleDeleteDocument(doc.id)}
-                                        className="p-2 text-red-500 hover:bg-red-50 rounded-full transition-colors"
-                                        title="Delete document"
-                                    >
-                                        <Trash2 className="w-4 h-4" />
-                                    </button>
+                                    <button
+                                        onClick={() => handleDeleteDocument(doc.id)}
+                                        disabled={deletingDocId === doc.id}
+                                        className="p-2 text-red-500 hover:bg-red-50 rounded-full transition-colors disabled:opacity-50"
+                                        title="Delete document"
+                                    >
+                                        {deletingDocId === doc.id ? (
+                                            <Loader2 className="w-4 h-4 animate-spin" />
+                                        ) : (
+                                            <Trash2 className="w-4 h-4" />
+                                        )}
+                                    </button>
src/pages/Dashboard/features/Tenants/TenantDetail.tsx (2)

29-29: Hardcoded external image URL for default avatar.

Using an external Unsplash URL as default image could:

  1. Create privacy concerns (external requests)
  2. Break if the URL becomes unavailable
  3. Slow down page loads for users with slow connections

Consider using a local placeholder image or inline SVG.


250-259: Fragile scroll behavior using setTimeout.

Using setTimeout with a fixed 100ms delay to wait for render is fragile and may not work reliably across different devices/network conditions. Consider using a ref callback or useLayoutEffect instead.

🔎 Alternative approach using ref
+    const profileSectionRef = useRef<HTMLDivElement>(null);
+
     // In the onClick handler:
-                                        onClick={() => {
-                                            setActiveTab('profile');
-                                            // Scroll to profile section after a brief delay to ensure tab is rendered
-                                            setTimeout(() => {
-                                                const profileSection = document.getElementById('profile-section');
-                                                if (profileSection) {
-                                                    profileSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
-                                                }
-                                            }, 100);
-                                        }}
+                                        onClick={() => {
+                                            setActiveTab('profile');
+                                            profileSectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
+                                        }}

// In JSX:
-                    <div id="profile-section">
+                    <div ref={profileSectionRef}>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2a12a8 and befc0a4.

📒 Files selected for processing (20)
  • src/pages/Dashboard/components/DashboardFilter.tsx
  • src/pages/Dashboard/features/Application/Application.tsx
  • src/pages/Dashboard/features/Equipments/Equipments.tsx
  • src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx
  • src/pages/Dashboard/features/Leads/leads.tsx
  • src/pages/Dashboard/features/Leases/Leases.tsx
  • src/pages/Dashboard/features/Listing/Listing.tsx
  • src/pages/Dashboard/features/Tasks/Tasks.tsx
  • src/pages/Dashboard/features/Tenants/TenantDetail.tsx
  • src/pages/Dashboard/features/Tenants/Tenants.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantApplicationsSection.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantCard.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantInsuranceSection.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantLeasesSection.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantProfileSection.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantRequestsSection.tsx
  • src/pages/Dashboard/features/Tenants/components/TenantTransactionsSection.tsx
  • src/pages/Dashboard/features/Units/Units.tsx
  • src/pages/Dashboard/features/Units/components/UnitGroupCard.tsx
  • src/pages/Dashboard/features/Units/components/UnitItem.tsx
🧰 Additional context used
🧬 Code graph analysis (11)
src/pages/Dashboard/features/Tenants/TenantDetail.tsx (2)
src/services/tenant.service.ts (1)
  • BackendTenantProfile (4-32)
src/hooks/useTenantQueries.ts (2)
  • useDeleteTenant (116-130)
  • useGetTenant (36-45)
src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Tenants/components/TenantLeasesSection.tsx (1)
src/services/leasing.service.ts (2)
  • BackendLeasing (4-29)
  • leasingService (298-298)
src/pages/Dashboard/features/Tenants/components/TenantApplicationsSection.tsx (2)
src/hooks/useApplicationQueries.ts (1)
  • useGetAllApplications (17-26)
src/services/application.service.ts (1)
  • BackendApplication (5-62)
src/pages/Dashboard/features/Equipments/Equipments.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Tasks/Tasks.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • SavedFilter (10-13)
src/pages/Dashboard/features/Units/Units.tsx (1)
src/services/listing.service.ts (1)
  • BackendListing (3-75)
src/pages/Dashboard/features/Application/Application.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Tenants/Tenants.tsx (3)
src/hooks/useTenantQueries.ts (2)
  • useDeleteTenant (116-130)
  • useGetAllTenants (22-31)
src/services/tenant.service.ts (2)
  • Tenant (78-84)
  • tenantService (484-484)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Tenants/components/TenantProfileSection.tsx (2)
src/hooks/useTenantQueries.ts (3)
  • useGetTenantDocuments (179-188)
  • useUploadTenantDocument (152-174)
  • useDeleteTenantDocument (193-205)
src/utils/formatDate.ts (1)
  • formatDate (1-10)
src/pages/Dashboard/features/Leases/Leases.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
🔇 Additional comments (45)
src/pages/Dashboard/features/Units/components/UnitItem.tsx (1)

16-16: LGTM!

The new optional hasDraftListing field extends the Unit interface to support draft listing tracking. This aligns with the broader filtering and listing-draft support being introduced across the dashboard.

src/pages/Dashboard/features/Tasks/Tasks.tsx (3)

40-51: LGTM!

Good implementation of saved filter tracking with activeSavedFilter state. The initial saved filter correctly uses date: ['today'] which aligns with the available filter options defined in filterOptions.date.


196-204: LGTM!

The handleSelectSavedFilter and handleClearSavedFilter handlers correctly manage the saved filter state. Setting activeSavedFilter on selection and clearing both filters and the active name on clear provides proper synchronization.


410-423: LGTM!

The DashboardFilter integration correctly passes onClearSavedFilter, activeSavedFilter, and initialFilters props, enabling bidirectional synchronization between the filter UI and the saved filter state.

src/pages/Dashboard/features/Tenants/components/TenantCard.tsx (2)

12-12: LGTM!

Good addition of the optional onDelete callback prop, enabling parent components to handle deletion with their own confirmation flow.

Also applies to: 21-22


51-61: No action needed—parent component properly implements confirmation flow.

The parent component (Tenants.tsx) correctly handles confirmation before triggering deletion. The handleDeleteTenant function (line 107-116) calls window.confirm and only proceeds with the deletion mutation if the user confirms. The onDelete callback (line 202-203) invokes this confirmation-protected function, ensuring users are always prompted before deletion occurs.

src/pages/Dashboard/features/Units/components/UnitGroupCard.tsx (1)

15-16: LGTM!

The new optional fields propertyStatus and balanceCategory extend the UnitGroup interface to support the expanded filtering dimensions being introduced across the Units feature.

src/pages/Dashboard/features/Leads/leads.tsx (3)

91-110: LGTM!

Good filtering of archived, removed, and inactive listings from the dropdown options. This ensures users only see relevant, active listings when filtering leads.


163-176: LGTM!

The optional chaining (?.length) provides safe handling when filter arrays are undefined. The filtering logic correctly matches leads against each selected filter criterion.


346-354: LGTM!

The DashboardFilter integration with initialFilters and showClearAll aligns with the broader pattern across dashboard features.

src/pages/Dashboard/features/Equipments/Equipments.tsx (4)

101-113: LGTM!

Good extension of filter state to include subcategory and unit dimensions, providing more granular filtering capabilities.


142-146: LGTM!

Good practice to preserve the original backendStatus for filtering while using the mapped status for display. This ensures accurate filtering against backend enum values.


174-209: LGTM!

The memoized unique collections (uniqueSubcategories, uniqueUnits) efficiently derive filter options from the transformed equipment data with proper type guards.


362-369: LGTM!

The DashboardFilter integration correctly passes initialFilters and showClearAll, aligning with the broader dashboard filtering pattern.

src/pages/Dashboard/features/Application/Application.tsx (4)

37-62: LGTM!

Good enrichment of the transformed card data with propertyUnit, propertyId, unitId, and backendStatus. The property/unit combination logic correctly handles both SINGLE and MULTI property types.


87-108: LGTM!

The memoized uniquePropertyUnits efficiently derives filter options from applications with proper null filtering.


158-166: LGTM!

The property/unit filter correctly ignores the __no_items__ placeholder value and only applies filtering when valid selections exist.


229-237: LGTM!

The DashboardFilter integration correctly passes initialFilters and showClearAll, maintaining consistency with other dashboard features.

src/pages/Dashboard/components/DashboardFilter.tsx (4)

26-27: LGTM!

Good extension of the public API with onClearSavedFilter and activeSavedFilter props to support saved filter state management from parent components.


96-99: LGTM!

Good UX to clear the active saved filter when the user manually changes filters, as the current selection no longer matches the saved preset.


163-190: LGTM!

The active saved filter badge provides clear visual feedback and the inline clear button offers a convenient way to reset. Good accessibility with the title attribute.


207-224: LGTM!

Good handling of the __no_items__ placeholder values by disabling the checkbox and updating styling to indicate non-interactivity.

src/pages/Dashboard/features/Tenants/Tenants.tsx (5)

1-8: LGTM!

Imports are correctly structured with React Query hooks (useGetAllTenants, useDeleteTenant) and UI components. The tenantService import provides the transformation utility.


62-85: Filter logic correctly ignores placeholder values.

The filtering implementation properly checks for and ignores __no_items__ placeholder values, ensuring they don't affect the filter results. The search matches against name, email, and phone fields appropriately.


174-188: Loading and error states are well-implemented.

The loading spinner with Loader2 and the error display with proper error message extraction provide good UX. The mutual exclusivity check (error && !isLoading) prevents showing error during loading.


191-217: Tenant grid rendering logic is correct.

The conditional rendering for empty states correctly distinguishes between "no tenants found" (no data) and "no tenants match your filters" (filtered out). Props are explicitly passed to TenantCard matching the expected interface.


14-26: Remove this comment. The tenantService import is actively used on line 25.

The code calls tenantService.transformTenant() and the method exists in src/services/tenant.service.ts with proper edge case handling for null/undefined values (email, phone, and image all have fallback values).

Likely an incorrect or invalid review comment.

src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx (3)

66-94: Comprehensive filtering logic implemented correctly.

The filter logic properly handles all filter dimensions (search, status, keyType, property, assignee) with appropriate empty-state checks using optional chaining and length validation.


104-129: Filter options and labels are well-structured.

Status and keyType options use consistent backend values matching the filtering logic. Dynamic options for property and assignee integrate properly.


195-196: DashboardFilter props added for consistency.

Adding initialFilters and showClearAll aligns with the pattern used across other dashboard features.

src/pages/Dashboard/features/Leases/Leases.tsx (3)

57-69: Filter state and placeholder options align with the codebase pattern.

Using Record<string, string[]> provides flexibility, and __no_items__ placeholders indicate filters without available data.


83-105: Search now includes lease number, filter logic correctly ignores placeholders.

The search extension to include lease.lease.toString() is good for numeric lease IDs. The placeholder ignore logic is consistent with other files.


200-202: DashboardFilter integration is consistent.

Props initialFilters and showClearAll are correctly passed.

src/pages/Dashboard/features/Listing/Listing.tsx (5)

22-23: Interface extended with daysListed and isSyndicated.

Good additions to track listing age and syndication status for filtering.


39-46: Filter state expanded for bedrooms and bathrooms.

Correctly initialized with empty arrays.


94-116: Filter options for bedrooms/bathrooms added.

Options include "5+" and "4+" for upper bounds, which the filtering logic handles correctly.


362-400: Filter logic handles all new dimensions correctly.

The bedrooms/bathrooms filtering properly handles the "5+"/"4+" cases with range checks. The combined filter predicate requires all conditions to match.


443-445: DashboardFilter props correctly added.

initialFilters and showClearAll are properly passed for filter state synchronization.

src/pages/Dashboard/features/Units/Units.tsx (5)

45-90: Listings map restructured for comprehensive listing tracking.

The refactor properly separates active and draft listings at both property and unit levels. The logic correctly prioritizes the most recent listing when duplicates exist.


125-132: Property status mapping with fallback.

The status mapping defaults to 'archived' for unknown or missing statuses. This is a safe default, but consider if 'inactive' or another status would be more appropriate for null/undefined backend values.


339-377: Enhanced filtering logic is comprehensive.

The filter implementation properly handles all new dimensions including status, occupancy, propertyType, marketingStatus, and balance. The marketing status logic correctly derives state from listing presence.


399-431: Filter options and labels updated consistently.

The options align with the filtering logic and backend data structures.


470-471: DashboardFilter props correctly added.

Consistent with other dashboard feature components.

src/pages/Dashboard/features/Tenants/TenantDetail.tsx (2)

14-67: LGTM - Well-structured data transformation.

The transformTenantForDetail function cleanly transforms backend data to the UI format with appropriate fallbacks for missing fields. The null coalescing and optional chaining are used correctly.


137-166: LGTM - Good loading and error state handling.

The component properly handles loading and error states with appropriate UI feedback. The error state includes a useful action button to navigate back.

Comment on lines +55 to +64
// Get unique properties and assignees for filter options
const propertyOptions = useMemo(() => {
const uniqueProperties = Array.from(new Set(transformedKeys.map(k => k.property).filter(Boolean)));
return uniqueProperties.map(prop => ({ value: prop, label: prop }));
}, [transformedKeys]);

const assigneeOptions = useMemo(() => {
const uniqueAssignees = Array.from(new Set(transformedKeys.map(k => k.assignee).filter(Boolean)));
return uniqueAssignees.map(assignee => ({ value: assignee, label: assignee }));
}, [transformedKeys]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, search for "__no_items__" as suggested in the review
rg -n "__no_items__" --type=tsx --type=ts

Repository: scalixity-dev/PMS_Frontend

Length of output: 97


🏁 Script executed:

# Also search for common empty state patterns in filter implementations
rg -n "no.*option|empty.*filter|placeholder" --type=tsx --type=ts -i | head -50

Repository: scalixity-dev/PMS_Frontend

Length of output: 97


🏁 Script executed:

# Let's examine the actual KeysLocks.tsx file to understand the context
wc -l src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx

Repository: scalixity-dev/PMS_Frontend

Length of output: 126


🏁 Script executed:

# Get the full context around lines 55-64, including how filters are used
cat -n src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx | sed -n '40,80p'

Repository: scalixity-dev/PMS_Frontend

Length of output: 2489


🏁 Script executed:

# Search for "__no_items__" without type filter
rg -n "__no_items__"

Repository: scalixity-dev/PMS_Frontend

Length of output: 2757


🏁 Script executed:

# Search for how filters are rendered in KeysLocks.tsx - look for more context
cat -n src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx | sed -n '100,150p'

Repository: scalixity-dev/PMS_Frontend

Length of output: 2163


🏁 Script executed:

# Look for Select or dropdown components using propertyOptions/assigneeOptions
cat -n src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx | sed -n '150,220p'

Repository: scalixity-dev/PMS_Frontend

Length of output: 3886


🏁 Script executed:

# Search for other filter implementations that might show empty state handling
rg -n "propertyOptions|assigneeOptions" src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx -A 3 -B 1

Repository: scalixity-dev/PMS_Frontend

Length of output: 851


🏁 Script executed:

# Look for common empty state patterns in Select/filter components
rg -n "no.*options|isMulti|isClearable" src/pages --type=js --type=jsx 2>/dev/null | head -20

Repository: scalixity-dev/PMS_Frontend

Length of output: 52


Add empty state placeholder for dynamic filter options to match established pattern.

The propertyOptions and assigneeOptions will be empty arrays if transformedKeys is empty, making those filters unusable. Other dashboard features (Equipments, Tenants, Leases, Application) handle this by adding a placeholder option with { value: '__no_items__', label: 'No X available' } when filter arrays are empty, then filtering out the placeholder during filtering logic. Apply the same pattern to maintain consistency.

Comment on lines +73 to +75
<div className="bg-[#7BD747] text-white px-4 py-2 rounded-full text-xs font-medium shadow-[inset_0_4px_1px_rgba(0,0,0,0.1)]">
{status}
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Status badge always shows green regardless of actual status.

The status badge on line 73 uses a fixed green color (#7BD747) for all statuses, but rejected/withdrawn applications should display differently. This creates a misleading UI where rejected applications appear successful.

🔎 Proposed fix
+                const getStatusColor = (status: string) => {
+                    switch (status) {
+                        case 'Approved': return 'bg-[#7BD747]';
+                        case 'Rejected': return 'bg-red-500';
+                        case 'Withdrawn': return 'bg-gray-500';
+                        case 'Draft': return 'bg-gray-400';
+                        default: return 'bg-orange-500'; // Pending states
+                    }
+                };
+
                 return (
                     <div key={app.id} className="bg-[#F6F6F8] rounded-[2rem] p-6 shadow-lg">
                         <div className="flex items-center justify-between">
                             <div>
                                 <h3 className="text-lg font-bold text-gray-800 mb-2">{applicantName}</h3>
                                 <p className="text-sm text-gray-600">Applied: {applicationDate}</p>
                                 <p className="text-sm text-gray-600">Status: {status}</p>
                             </div>
-                            <div className="bg-[#7BD747] text-white px-4 py-2 rounded-full text-xs font-medium shadow-[inset_0_4px_1px_rgba(0,0,0,0.1)]">
+                            <div className={`${getStatusColor(status)} text-white px-4 py-2 rounded-full text-xs font-medium shadow-[inset_0_4px_1px_rgba(0,0,0,0.1)]`}>
                                 {status}
                             </div>
                         </div>
                     </div>
                 );
📝 Committable suggestion

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

Suggested change
<div className="bg-[#7BD747] text-white px-4 py-2 rounded-full text-xs font-medium shadow-[inset_0_4px_1px_rgba(0,0,0,0.1)]">
{status}
</div>
const getStatusColor = (status: string) => {
switch (status) {
case 'Approved': return 'bg-[#7BD747]';
case 'Rejected': return 'bg-red-500';
case 'Withdrawn': return 'bg-gray-500';
case 'Draft': return 'bg-gray-400';
default: return 'bg-orange-500'; // Pending states
}
};
return (
<div key={app.id} className="bg-[#F6F6F8] rounded-[2rem] p-6 shadow-lg">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-bold text-gray-800 mb-2">{applicantName}</h3>
<p className="text-sm text-gray-600">Applied: {applicationDate}</p>
<p className="text-sm text-gray-600">Status: {status}</p>
</div>
<div className={`${getStatusColor(status)} text-white px-4 py-2 rounded-full text-xs font-medium shadow-[inset_0_4px_1px_rgba(0,0,0,0.1)]`}>
{status}
</div>
</div>
</div>
);
🤖 Prompt for AI Agents
In src/pages/Dashboard/features/Tenants/components/TenantApplicationsSection.tsx
around lines 73 to 75, the status badge uses a hard-coded green background
(#7BD747) so all statuses appear successful; replace the fixed color with a
status-to-class mapping (normalize status string then map e.g. approved/accepted
-> green classes, pending/in-review -> yellow/amber classes, rejected/withdrawn
-> red classes, and a neutral/default color for unknown statuses) and use that
computed className for the badge while preserving existing
text/spacing/rounded/shadow styles so the badge color reflects the actual
status.

Comment on lines +25 to +27
} catch (error) {
console.error('Failed to fetch leases:', error);
} finally {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent error handling leaves users uninformed.

Errors are only logged to console. Users see an empty state without knowing a fetch failed. Add error state UI similar to the loading state.

🔎 Proposed fix
 const TenantLeasesSection = ({ tenantId, tenant }: TenantLeasesSectionProps) => {
     const navigate = useNavigate();
     const [allLeases, setAllLeases] = useState<BackendLeasing[]>([]);
     const [isLoading, setIsLoading] = useState(true);
+    const [error, setError] = useState<string | null>(null);
     
     useEffect(() => {
         const fetchLeases = async () => {
             try {
                 setIsLoading(true);
+                setError(null);
                 const leases = await leasingService.getAll();
                 setAllLeases(leases);
             } catch (error) {
                 console.error('Failed to fetch leases:', error);
+                setError('Failed to load leases. Please try again.');
             } finally {
                 setIsLoading(false);
             }
         };
         fetchLeases();
     }, []);
+
+    if (error) {
+        return (
+            <div className="text-center py-12 bg-red-50 rounded-[2rem]">
+                <p className="text-red-600">{error}</p>
+            </div>
+        );
+    }
📝 Committable suggestion

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

Suggested change
} catch (error) {
console.error('Failed to fetch leases:', error);
} finally {
const TenantLeasesSection = ({ tenantId, tenant }: TenantLeasesSectionProps) => {
const navigate = useNavigate();
const [allLeases, setAllLeases] = useState<BackendLeasing[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchLeases = async () => {
try {
setIsLoading(true);
setError(null);
const leases = await leasingService.getAll();
setAllLeases(leases);
} catch (error) {
console.error('Failed to fetch leases:', error);
setError('Failed to load leases. Please try again.');
} finally {
setIsLoading(false);
}
};
fetchLeases();
}, []);
if (error) {
return (
<div className="text-center py-12 bg-red-50 rounded-[2rem]">
<p className="text-red-600">{error}</p>
</div>
);
}
🤖 Prompt for AI Agents
In src/pages/Dashboard/features/Tenants/components/TenantLeasesSection.tsx
around lines 25-27, the catch only logs errors to console leaving users with an
empty state; add an error state (e.g., useState<string | null> error) and set it
in the catch with a user-friendly message (and optionally the error.message),
ensure finally still clears loading as appropriate, and update the render to
show an error UI (reusing the loading state layout) when error is set — include
a retry action/button that re-invokes the fetch and clears the error when
retried.

Comment on lines +37 to +57
// Transform leases for display
// Note: BackendLeasing doesn't have tenantId, so we'll show all leases
// In a real scenario, leases would be linked to tenants through applications or a tenantId field
const leases = useMemo(() => {
return allLeases.map((lease: BackendLeasing) => {
const propertyName = lease.property?.propertyName || 'Unknown Property';
const createdAt = new Date(lease.createdAt);
const date = createdAt.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
const time = createdAt.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', hour12: true });

return {
id: lease.id,
tenantName: tenant.name,
tenantInitial: tenantInitial,
date: date,
time: time,
status: 'Active', // BackendLeasing doesn't have status, defaulting to Active
property: propertyName
};
});
}, [allLeases, tenant.name, tenantInitial]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

All leases displayed regardless of tenant - potential data exposure concern.

The comment on lines 38-39 acknowledges that BackendLeasing lacks tenantId, but the current implementation shows all leases to every tenant. This could expose unrelated lease information to users and is misleading since this is a "Tenant Leases Section".

Consider either:

  1. Filtering leases based on a relationship (e.g., through applications)
  2. Not rendering this section until proper tenant-lease linking is available
  3. Adding a clear UI indicator that these are "all leases" not "this tenant's leases"

Comment on lines +24 to +25
return {
id: Number(backendTenant.id) || 0, // Convert string ID to number for compatibility

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for tenant ID usage patterns and type definitions
rg -n "tenantId|tenant\.id" --type=ts --type=tsx -C 2 | head -100

Repository: scalixity-dev/PMS_Frontend

Length of output: 97


🏁 Script executed:

# Look for Tenant interface/type definitions
rg -n "interface Tenant|type Tenant|backendTenant" --type=ts --type=tsx -A 5 | head -80

Repository: scalixity-dev/PMS_Frontend

Length of output: 97


🏁 Script executed:

# Check the TenantDetail.tsx file directly
cat -n src/pages/Dashboard/features/Tenants/TenantDetail.tsx | head -60

Repository: scalixity-dev/PMS_Frontend

Length of output: 3488


🏁 Script executed:

# Search for API/backend responses to understand data format
rg -n "id.*string|id.*number|uuid|UUID" --type=ts --type=tsx src/pages/Dashboard/features/Tenants/ -A 1 -B 1

Repository: scalixity-dev/PMS_Frontend

Length of output: 97


🏁 Script executed:

# Find the tenant.service file to check the BackendTenantProfile type
find . -name "tenant.service*" -type f

Repository: scalixity-dev/PMS_Frontend

Length of output: 102


🏁 Script executed:

# Search for tenant ID patterns more broadly
rg "tenantId|tenant\.id" -A 2 -B 2 | head -80

Repository: scalixity-dev/PMS_Frontend

Length of output: 6475


🏁 Script executed:

# Look for BackendTenantProfile definition
rg "BackendTenantProfile|interface.*Tenant" -A 10 | head -100

Repository: scalixity-dev/PMS_Frontend

Length of output: 5405


🏁 Script executed:

# Check if IDs are numeric or strings in responses
rg "\"id\".*:|id.*Number|id.*string" -A 1 -B 1 | head -80

Repository: scalixity-dev/PMS_Frontend

Length of output: 5955


Remove unnecessary Number() conversion for tenant ID.

BackendTenantProfile.id is already a string type from the backend. Converting it with Number(backendTenant.id) || 0 will fail for non-numeric IDs (e.g., UUIDs), resulting in NaN || 0, which makes the ID become 0. This breaks all downstream operations since child components expect the actual string ID. Keep the ID as a string to match the backend type and the frontend Tenant interface definition.

🤖 Prompt for AI Agents
In src/pages/Dashboard/features/Tenants/TenantDetail.tsx around lines 24-25,
remove the Number() conversion and preserve the backend string ID; replace "id:
Number(backendTenant.id) || 0" with a string-preserving expression such as "id:
backendTenant.id || ''" (or ensure the returned value matches the Tenant
interface, e.g., backendTenant.id ?? ''), so non-numeric IDs (UUIDs) are not
turned into 0 and downstream components receive the actual string ID.

@Nihalpuse
Nihalpuse merged commit 6ef27ae into main Jan 3, 2026
1 check was pending

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
src/pages/Dashboard/features/Tenants/Tenants.tsx (1)

216-229: Bug: Using wrong loading variable name.

Line 216 uses loading but the React Query hook returns isLoading. This will cause the loading state to never show (since loading would be undefined/falsy after removing the duplicate declaration).

🔎 Proposed fix
-                {!loading && !error && (
+                {!isLoading && !error && (

Also update the onDeleteSuccess prop on TenantCard (line 228) - if using React Query, the mutation's onSuccess handles cache invalidation automatically, so fetchTenants reference is no longer needed:

-                                    onDeleteSuccess={fetchTenants}
+                                    onDelete={() => handleDeleteTenant(tenant.id)}
src/pages/Dashboard/components/DashboardFilter.tsx (1)

182-263: Critical: Malformed JSX structure will cause parse/render errors.

The static analyzer reports a parse error at line 263. Examining the code, there appears to be duplicate/misplaced JSX: lines 182-263 contain filter button and dropdown rendering code that appears outside the main desktop filter bar <div> (which starts at line 214). This creates invalid JSX structure with orphaned elements.

The active saved filter badge (lines 182-210) and filter dropdowns (lines 211-263) appear to be incorrectly placed between the mobile filter bar and desktop filter bar sections.

🔎 Analysis and suggested fix

The code structure should be:

  1. Mobile filter bar (lines 156-180) ✓
  2. Desktop filter bar starting at line 214

But lines 182-263 inject content that breaks this structure. The active saved filter badge (lines 184-210) should likely be inside the desktop filter bar's <div className="flex items-center gap-2"> section around line 265.

Please verify the intended placement and restructure the JSX. The badge and dropdown code needs to be moved inside the appropriate container element.

             {/* Mobile & Tablet Search & Filter Bar */}
             <div className="lg:hidden bg-white rounded-full flex items-center gap-2 p-2 mb-4 shadow-sm">
                 {/* ... mobile content ... */}
             </div>
 
-            <div className="flex items-center gap-2">
-                {/* Display active saved filter badge */}
-                {activeSavedFilter && (
-                    {/* ... badge content ... */}
-                )}
-                {Object.keys(filterOptions).map((filterType) => (
-                    {/* ... misplaced dropdown code ... */}
-                ))}
-            </div>
-
             {/* Desktop Filter Bar */}
             <div ref={dropdownRef} className="hidden lg:flex bg-[#3A6D6C] p-4 rounded-full items-center gap-4 mb-8 justify-between relative shadow-md">
+                {/* Move badge and filter buttons inside here */}
♻️ Duplicate comments (1)
src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx (1)

59-68: Add empty state placeholder for dynamic filter options to match established pattern.

The propertyOptions and assigneeOptions will be empty arrays if transformedKeys is empty, making those filters unusable. Other dashboard features handle this by adding a placeholder option with { value: '__no_items__', label: 'No X available' }. Apply the same pattern for consistency.

🔎 Proposed fix
     const filterOptions: Record<string, FilterOption[]> = {
         status: [
             { value: 'AVAILABLE', label: 'Available' },
             { value: 'ISSUED', label: 'Issued' },
             { value: 'LOST', label: 'Lost' },
             { value: 'DAMAGED', label: 'Damaged' },
             { value: 'INACTIVE', label: 'Inactive' },
         ],
         keyType: [
             { value: 'DOOR', label: 'Main Door' },
             { value: 'MAILBOX', label: 'Mailbox' },
             { value: 'GARAGE', label: 'Garage' },
             { value: 'GATE', label: 'Gate' },
             { value: 'STORAGE', label: 'Storage' },
             { value: 'OTHER', label: 'Other' },
         ],
-        property: propertyOptions,
-        assignee: assigneeOptions,
+        property: propertyOptions.length > 0 ? propertyOptions : [
+            { value: '__no_items__', label: 'No properties available' }
+        ],
+        assignee: assigneeOptions.length > 0 ? assigneeOptions : [
+            { value: '__no_items__', label: 'No assignees available' }
+        ],
     };

Then update the filtering logic to ignore placeholders:

             // Property filter
-            const matchesProperty = !filters.property?.length ||
-                filters.property.includes(key.property);
+            const matchesProperty = !filters.property?.length ||
+                filters.property.filter(v => v !== '__no_items__').length === 0 ||
+                filters.property.includes(key.property);

             // Assignee filter
-            const matchesAssignee = !filters.assignee?.length ||
-                filters.assignee.includes(key.assignee);
+            const matchesAssignee = !filters.assignee?.length ||
+                filters.assignee.filter(v => v !== '__no_items__').length === 0 ||
+                filters.assignee.includes(key.assignee);
🧹 Nitpick comments (5)
src/pages/Dashboard/features/Equipments/Equipments.tsx (1)

219-228: Consider consistency: property filter uses empty array fallback while others use __no_items__ placeholder.

The property filter falls back to an empty array when no properties exist (Line 225), while category, subcategory, and unit use the __no_items__ placeholder pattern. This inconsistency means the property dropdown may appear empty rather than showing a helpful "No properties available" message.

🔎 Suggested fix for consistency
-        property: uniqueProperties.length > 0 ? uniqueProperties : [],
+        property: uniqueProperties.length > 0 ? uniqueProperties : [
+            { value: '__no_items__', label: 'No properties available' }
+        ],
src/pages/Dashboard/features/Tenants/Tenants.tsx (1)

132-141: Consider UX improvement: Replace window.confirm with a styled modal.

Using window.confirm is functional but doesn't match the polished modal UI seen in other components (e.g., DeleteConfirmationModal in Tasks, custom delete modal in Equipments). Consider using a consistent styled modal for better UX.

src/pages/Dashboard/features/Listing/Listing.tsx (1)

369-374: Unlisted items are excluded when "Days Listed" filters are active.

When any daysListed filter is selected, unlisted items (where listing.daysListed is undefined) will be filtered out. This may be intentional but could surprise users who expect unlisted items to appear alongside filtered listings.

If unlisted items should be included regardless of the days filter, consider:

🔎 Proposed fix
-            const matchesDaysListed = !filters.daysListed?.length || 
-                (listing.daysListed !== undefined && (
+            const matchesDaysListed = !filters.daysListed?.length ||
+                listing.daysListed === undefined || // Include unlisted items
+                (
                     (filters.daysListed.includes('new') && listing.daysListed < 7) ||
                     (filters.daysListed.includes('recent') && listing.daysListed >= 7 && listing.daysListed <= 30) ||
                     (filters.daysListed.includes('old') && listing.daysListed > 30)
-                ));
+                );
src/pages/Dashboard/features/Units/Units.tsx (2)

128-136: Consider handling 'INACTIVE' status explicitly.

Per the BackendListing interface, property status can be 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' | null. The statusMap doesn't include 'INACTIVE', so it falls back to 'archived'. If 'INACTIVE' should be treated differently, consider adding it to the map.

🔎 Proposed fix
                 const statusMap: Record<string, 'active' | 'archived'> = {
                     'ACTIVE': 'active',
                     'ARCHIVED': 'archived',
+                    'INACTIVE': 'archived', // Explicitly map INACTIVE
                 };

270-325: Some currencies lack dedicated thresholds.

The countryToCurrency map includes currencies like NZD, CHF, ZAR, BRL, MXN that don't have entries in currencyThresholds. These will fall back to USD thresholds, which may not be appropriate for their purchasing power.

🔎 Proposed fix - add missing thresholds
                 const currencyThresholds: Record<string, { low: number; high: number }> = {
                     'USD': { low: 25000, high: 75000 },
                     'CAD': { low: 33000, high: 100000 },
                     'GBP': { low: 20000, high: 60000 },
                     'EUR': { low: 23000, high: 69000 },
                     'AUD': { low: 37000, high: 110000 },
+                    'NZD': { low: 40000, high: 120000 },
                     'INR': { low: 2000000, high: 6000000 },
                     'CNY': { low: 180000, high: 540000 },
                     'JPY': { low: 3500000, high: 10500000 },
+                    'CHF': { low: 22000, high: 66000 },
                     'SGD': { low: 33000, high: 100000 },
                     'HKD': { low: 195000, high: 585000 },
                     'AED': { low: 92000, high: 275000 },
                     'SAR': { low: 94000, high: 280000 },
+                    'ZAR': { low: 450000, high: 1350000 },
+                    'BRL': { low: 125000, high: 375000 },
+                    'MXN': { low: 430000, high: 1290000 },
                 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between befc0a4 and 0a0bd99.

📒 Files selected for processing (11)
  • src/pages/Dashboard/components/DashboardFilter.tsx
  • src/pages/Dashboard/features/Application/Application.tsx
  • src/pages/Dashboard/features/Equipments/Equipments.tsx
  • src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx
  • src/pages/Dashboard/features/Leads/leads.tsx
  • src/pages/Dashboard/features/Leases/Leases.tsx
  • src/pages/Dashboard/features/Listing/Listing.tsx
  • src/pages/Dashboard/features/Tasks/Tasks.tsx
  • src/pages/Dashboard/features/Tenants/Tenants.tsx
  • src/pages/Dashboard/features/Units/Units.tsx
  • src/pages/Dashboard/features/Units/components/UnitGroupCard.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/Dashboard/features/Units/components/UnitGroupCard.tsx
  • src/pages/Dashboard/features/Leads/leads.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Tenants/Tenants.tsx (3)
src/hooks/useTenantQueries.ts (2)
  • useDeleteTenant (116-130)
  • useGetAllTenants (22-31)
src/services/tenant.service.ts (2)
  • Tenant (78-84)
  • tenantService (484-484)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Leases/Leases.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Equipments/Equipments.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Application/Application.tsx (1)
src/pages/Dashboard/components/DashboardFilter.tsx (1)
  • FilterOption (5-8)
src/pages/Dashboard/features/Units/Units.tsx (1)
src/services/listing.service.ts (1)
  • BackendListing (3-75)
🪛 Biome (2.1.2)
src/pages/Dashboard/features/Tenants/Tenants.tsx

[error] 23-23: Shouldn't redeclare 'setFilters'. Consider to delete it or rename it.

'setFilters' is defined here:

(lint/suspicious/noRedeclare)

src/pages/Dashboard/components/DashboardFilter.tsx

[error] 263-263: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)

🔇 Additional comments (33)
src/pages/Dashboard/features/Tasks/Tasks.tsx (3)

41-54: LGTM! Saved filter state management is well implemented.

The activeSavedFilter state tracking and the updated saved filter default (date: ['today'] instead of frequency: ['DAILY']) align with the DashboardFilter's new props. The initialization and state flow are correct.


209-217: LGTM! Clear and select handlers properly synchronize state.

The handleSelectSavedFilter correctly sets both the filters and the active filter name, while handleClearSavedFilter properly resets both states.


418-432: LGTM! DashboardFilter integration is complete.

All new props (onClearSavedFilter, activeSavedFilter, initialFilters, showClearAll) are correctly wired to the component.

src/pages/Dashboard/features/Equipments/Equipments.tsx (5)

102-114: LGTM! Extended filter state structure is correct.

The filter state now properly supports the new subcategory and unit dimensions with proper initialization.


137-159: LGTM! Equipment transformation is well structured.

The transformation correctly preserves backendStatus for filtering while mapping display status, and properly extracts propertyId, unit, and unitId fields.


175-210: LGTM! Derived filter options are correctly computed.

The uniqueSubcategories and uniqueUnits memos properly filter out empty strings and create the expected { value, label } structure.


244-281: LGTM! Filtering logic correctly handles backend status and placeholders.

The filtering uses backendStatus for status matching and properly ignores __no_items__ placeholder values across all filter dimensions.


363-370: LGTM! DashboardFilter integration is complete.

The initialFilters and showClearAll props are properly passed to enable persistent filter state and the clear-all option.

src/pages/Dashboard/features/Leases/Leases.tsx (3)

59-72: LGTM! Filter state and placeholder options are correctly implemented.

The generic Record<string, string[]> type aligns with the DashboardFilter API, and the __no_items__ placeholders clearly communicate when no data is available for occupancy and property type filters.


85-107: LGTM! Filtering logic handles placeholders correctly.

The search now includes tenant and lease fields, and the occupancy/propertyType filters properly ignore placeholder values when evaluating matches.


198-205: LGTM! DashboardFilter integration is complete.

Props are correctly wired with initialFilters and showClearAll.

src/pages/Dashboard/features/Tenants/Tenants.tsx (1)

87-119: LGTM! Filtering and sorting logic is well implemented.

The search covers name, email, and phone fields. The placeholder value handling correctly ignores __no_items__ values, and the sorting respects the sortOrder state.

src/pages/Dashboard/features/KeysLocks/KeysLocks.tsx (3)

36-57: LGTM! Extended key transformation is well structured.

The transformation correctly preserves keyType for filtering while displaying the mapped type, and includes propertyId and unitId for potential future use.


70-98: LGTM! Filtering logic is comprehensive.

The search includes assignee, and all filter dimensions (status, keyType, property, assignee) are correctly applied with AND logic.


220-227: LGTM! DashboardFilter integration is complete.

Props are correctly wired with initialFilters and showClearAll.

src/pages/Dashboard/components/DashboardFilter.tsx (3)

26-27: LGTM! New props for saved filter management are well typed.

The onClearSavedFilter callback and activeSavedFilter state prop enable parent components to manage saved filter state externally.

Also applies to: 41-43


76-83: LGTM! Filter synchronization effect is correct.

The effect properly syncs selectedFilters with initialFilters when either changes, initializing missing keys to empty arrays.


110-113: LGTM! Saved filter clearing on manual changes is well implemented.

Both handleFilterToggle and handleClearAll correctly clear the active saved filter when the user manually modifies filters, providing intuitive UX.

Also applies to: 127-130

src/pages/Dashboard/features/Application/Application.tsx (4)

37-62: LGTM! Application transformation is well structured.

The transformation correctly builds the propertyUnit display string (handling MULTI vs SINGLE property types), preserves backendStatus for potential future use, and includes propertyId and unitId.


88-129: LGTM! Dynamic filter options and placeholders are correctly implemented.

The uniquePropertyUnits computation properly extracts and deduplicates property/unit combinations, and the fallback to __no_items__ placeholder when empty maintains consistency with other dashboard features.


135-173: LGTM! Filtering logic correctly handles placeholders and status mapping.

The search now includes propertyUnit, the screeningStatus filter uses the correct mapped status, and the propertyUnits filter properly ignores the __no_items__ placeholder.


230-238: LGTM! DashboardFilter integration is complete.

Props are correctly wired with initialFilters and showClearAll.

src/pages/Dashboard/features/Listing/Listing.tsx (6)

22-23: LGTM!

Good addition of daysListed and isSyndicated fields to the interface. These are properly typed as optional, which correctly reflects that they're only populated for active listings.


40-47: LGTM!

Filter state initialization with empty arrays for the new bedroom and bathroom filters is correct.


96-108: LGTM!

Filter options for bedrooms and bathrooms are well-defined. The "5+" and "4+" special values align with the filtering logic in filteredListings.


187-197: LGTM!

Days listed calculation is correct. The logic properly handles cases where there's no active listing by leaving daysListed as undefined.


377-379: LGTM!

Syndication filter logic correctly treats unlisted items as "not syndicated" since isSyndicated is set to false for them during transformation.


439-447: LGTM!

The integration with DashboardFilter properly passes initialFilters and enables showClearAll, maintaining filter state consistency.

src/pages/Dashboard/features/Units/Units.tsx (5)

48-93: LGTM!

Good refactoring from a single activeListingsMap to a structured listingsMap object with four maps. This cleanly separates active vs draft and property vs unit listings while maintaining the "most recent by listedAt" logic.


364-373: LGTM!

The marketing status filter logic correctly distinguishes between listed (active), unlisted (no listings), and draft states by checking unit-level listing flags.


402-426: LGTM!

Filter options are well-defined and align with the filtering logic. The values match backend enums where applicable.


473-480: LGTM!

Consistent integration with DashboardFilter, matching the pattern used in Listing.tsx.


327-340: No action required. The UnitGroup interface already includes both propertyStatus?: 'active' | 'archived' and balanceCategory?: 'low' | 'medium' | 'high' fields (lines 15-16 of UnitGroupCard.tsx), which correctly align with the returned object.

Comment on lines +391 to +399
// Bathrooms filter
const matchesBathrooms = !filters.bathrooms?.length ||
filters.bathrooms.some(filterValue => {
if (filterValue === '4+') {
return listing.bathrooms >= 4;
}
const filterBathrooms = parseInt(filterValue, 10);
return listing.bathrooms === filterBathrooms;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the full context of the Listing.tsx file around lines 391-399
wc -l src/pages/Dashboard/features/Listing/Listing.tsx

Repository: scalixity-dev/PMS_Frontend

Length of output: 122


🏁 Script executed:

# Read the relevant section with context around lines 391-399
sed -n '370,420p' src/pages/Dashboard/features/Listing/Listing.tsx

Repository: scalixity-dev/PMS_Frontend

Length of output: 2334


🏁 Script executed:

# Look for the bedrooms filter logic to compare (mentioned in the scratchpad)
sed -n '375,410p' src/pages/Dashboard/features/Listing/Listing.tsx | cat -n

Repository: scalixity-dev/PMS_Frontend

Length of output: 1854


🏁 Script executed:

# Search for how listing data is structured and where bathrooms comes from
rg -n "bathrooms" src/pages/Dashboard/features/Listing/Listing.tsx -B 2 -A 2

Repository: scalixity-dev/PMS_Frontend

Length of output: 2759


🏁 Script executed:

# Search for any data types or interfaces that define listing structure
rg -n "interface.*[Ll]isting|type.*[Ll]isting" --type ts -C 3 | head -50

Repository: scalixity-dev/PMS_Frontend

Length of output: 4309


🏁 Script executed:

# Check if there are any tests for the filtering logic
find . -name "*.test.ts*" -o -name "*.spec.ts*" | xargs grep -l "bathrooms\|filter" 2>/dev/null | head -10

Repository: scalixity-dev/PMS_Frontend

Length of output: 52


Fractional bathroom and bedroom values won't match filter options.

The filters use exact equality checks after parsing integer filter values, but listing data is loaded with parseFloat() which allows fractional values (e.g., 1.5, 2.5 bathrooms). A listing with 1.5 bathrooms won't match the "1" filter option. The same issue affects the bedrooms filter. Consider using Math.floor() on listing values before comparison so fractional values match their floor integer (e.g., 1.5 matches "1").

🤖 Prompt for AI Agents
In src/pages/Dashboard/features/Listing/Listing.tsx around lines 391 to 399, the
bathroom (and similarly bedroom) filter compares parsed integer filter values to
listing.bathrooms which may be fractional, so 1.5 won't match the "1" filter;
change the comparisons to use Math.floor(listing.bathrooms) (and
Math.floor(listing.bedrooms) for the bedrooms filter) before comparing to parsed
integers, keep the existing '4+' branch checking >= 4, and ensure you still
parse filter strings with parseInt(filterValue, 10).

Comment on lines +14 to +28
const [searchQuery, setSearchQuery] = useState('');
const [filters, setFilters] = useState<Record<string, string[]>>({});
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 9;
const deleteTenantMutation = useDeleteTenant();

// Fetch tenants using React Query
const { data: backendTenants = [], isLoading, error } = useGetAllTenants();
const { sidebarCollapsed = false } = useOutletContext<{ sidebarCollapsed: boolean }>() ?? {};
const [, setFilters] = useState<Record<string, string[]>>({});

const handleSearchChange = (_search: string) => {
// console.log('Search:', search);
// Transform backend tenants to frontend format
const tenants: Tenant[] = useMemo(() => {
return backendTenants.map((tenant) => tenantService.transformTenant(tenant));
}, [backendTenants]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Duplicate state declarations will cause runtime errors.

The React Query integration adds new state (filters on line 15, tenants on line 26) but the old manual implementation still exists below (lines 58-79), causing duplicate declarations. The static analysis correctly flags line 23 redeclaring setFilters.

This will cause a runtime error: SyntaxError: Identifier 'setFilters' has already been declared.

🔎 Proposed fix: Remove duplicate declarations and old fetch logic

Remove lines 23, 58-79 (the duplicate state declarations and the old fetchTenants function):

     const { sidebarCollapsed = false } = useOutletContext<{ sidebarCollapsed: boolean }>() ?? {};
-    const [, setFilters] = useState<Record<string, string[]>>({});

     // Transform backend tenants to frontend format
     const tenants: Tenant[] = useMemo(() => {
         return backendTenants.map((tenant) => tenantService.transformTenant(tenant));
     }, [backendTenants]);
     
     // ... other code ...
     
-    const [tenants, setTenants] = useState<Tenant[]>([]);
-    const [loading, setLoading] = useState(true);
-    const [error, setError] = useState<string | null>(null);
-
-    const fetchTenants = async () => {
-        try {
-            setLoading(true);
-            setError(null);
-            const backendTenants = await tenantService.getAll();
-            const transformedTenants = backendTenants.map((tenant) => tenantService.transformTenant(tenant));
-            setTenants(transformedTenants);
-        } catch (err) {
-            console.error('Error fetching tenants:', err);
-            setError(err instanceof Error ? err.message : 'Failed to fetch tenants');
-        } finally {
-            setLoading(false);
-        }
-    };
-
-    useEffect(() => {
-        fetchTenants();
-    }, []);

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 23-23: Shouldn't redeclare 'setFilters'. Consider to delete it or rename it.

'setFilters' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In src/pages/Dashboard/features/Tenants/Tenants.tsx around lines 14 to 28, there
is a duplicate state declaration for filters (the second "const [, setFilters] =
useState..." on line ~23) that conflicts with the earlier "const [filters,
setFilters] = useState..." and the file still contains the old manual
fetchTenants logic further down (lines ~58-79); remove the duplicate state
declaration (the anonymous array destructure that redeclares setFilters) and
delete the obsolete fetchTenants implementation and any related manual state
handling (lines 58-79), then ensure the component uses the React Query data
(backendTenants -> tenants via tenantService.transformTenant) and the single
filters state declared at the top.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants