@salesforce/webapp-template-app-react-sample-b2e-experimental 1.64.0 → 1.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/data/Agent__c.json +79 -0
  3. package/dist/force-app/main/default/data/Application__c.json +10 -10
  4. package/dist/force-app/main/default/data/Contact.json +44 -0
  5. package/dist/force-app/main/default/data/Maintenance_Request__c.json +30 -30
  6. package/dist/force-app/main/default/data/Property__c.json +25 -25
  7. package/dist/force-app/main/default/data/data-plan.json +13 -1
  8. package/dist/force-app/main/default/objects/Agent__c/Agent__c.object-meta.xml +66 -0
  9. package/dist/force-app/main/default/objects/Agent__c/fields/Agent_Type__c.field-meta.xml +37 -0
  10. package/dist/force-app/main/default/objects/Agent__c/fields/Availability__c.field-meta.xml +37 -0
  11. package/dist/force-app/main/default/objects/Agent__c/fields/Emergency_Alt__c.field-meta.xml +11 -0
  12. package/dist/force-app/main/default/objects/Agent__c/fields/Language__c.field-meta.xml +42 -0
  13. package/dist/force-app/main/default/objects/Agent__c/fields/License_Expiry__c.field-meta.xml +11 -0
  14. package/dist/force-app/main/default/objects/Agent__c/fields/License_Number__c.field-meta.xml +14 -0
  15. package/dist/force-app/main/default/objects/Agent__c/fields/Office_Location__c.field-meta.xml +42 -0
  16. package/dist/force-app/main/default/objects/Agent__c/fields/Territory__c.field-meta.xml +42 -0
  17. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/User__c.field-meta.xml +1 -1
  18. package/dist/force-app/main/default/objects/Property__c/fields/Agent__c.field-meta.xml +1 -1
  19. package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +53 -25
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2e/package-lock.json +18307 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +9 -7
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/applications.ts +142 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/dashboard.ts +1 -2
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenance.ts +63 -6
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/properties.ts +1 -2
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/appLayout.tsx +4 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/AgentforceConversationClient.tsx +127 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationDetailsModal.tsx +150 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationsTable.tsx +105 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceDetailsModal.tsx +191 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/PropertyDetailsModal.tsx +274 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/StatusBadge.tsx +17 -17
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/TopBar.tsx +37 -7
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +1 -5
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/index.ts +6 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/types.ts +6 -2
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Applications.tsx +129 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +28 -13
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +95 -62
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Properties.tsx +22 -2
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/TestAccPage.tsx +19 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/routes.tsx +12 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/types/conversation.ts +21 -0
  44. package/dist/package.json +1 -1
  45. package/package.json +2 -4
  46. package/dist/force-app/main/default/applications/Property_Management.app-meta.xml +0 -26
  47. package/dist/force-app/main/default/tabs/Application__c.tab-meta.xml +0 -6
  48. package/dist/force-app/main/default/tabs/Maintenance_Request__c.tab-meta.xml +0 -7
  49. package/dist/force-app/main/default/tabs/Maintenance_Worker__c.tab-meta.xml +0 -6
  50. package/dist/force-app/main/default/tabs/Notification__c.tab-meta.xml +0 -6
  51. package/dist/force-app/main/default/tabs/Property__c.tab-meta.xml +0 -7
  52. package/dist/force-app/main/default/tabs/Tenant__c.tab-meta.xml +0 -7
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/utils.ts +0 -4
@@ -0,0 +1,105 @@
1
+ import React from "react";
2
+ import { Card } from "@/components/ui/card";
3
+ import type { Application } from "../lib/types.js";
4
+
5
+ interface ApplicationsTableProps {
6
+ applications: Application[];
7
+ onRowClick: (application: Application) => void;
8
+ }
9
+
10
+ const getStatusColor = (status: string) => {
11
+ const statusLower = status.toLowerCase();
12
+ if (statusLower.includes("approved")) return "bg-green-100 text-green-700";
13
+ if (statusLower.includes("rejected")) return "bg-red-100 text-red-700";
14
+ if (statusLower.includes("background")) return "bg-blue-100 text-blue-700";
15
+ if (statusLower.includes("review")) return "bg-yellow-100 text-yellow-700";
16
+ return "bg-gray-100 text-gray-700";
17
+ };
18
+
19
+ export const ApplicationsTable: React.FC<ApplicationsTableProps> = ({
20
+ applications,
21
+ onRowClick,
22
+ }) => {
23
+ const formatDate = (dateString: string) => {
24
+ if (!dateString) return "N/A";
25
+ try {
26
+ const date = new Date(dateString);
27
+ return date.toLocaleDateString("en-US", {
28
+ year: "numeric",
29
+ month: "short",
30
+ day: "numeric",
31
+ });
32
+ } catch {
33
+ return dateString;
34
+ }
35
+ };
36
+
37
+ return (
38
+ <Card className="border-gray-200 shadow-sm">
39
+ <div className="overflow-x-auto">
40
+ <table className="w-full">
41
+ <thead className="bg-gray-50 border-b border-gray-200">
42
+ <tr>
43
+ <th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
44
+ User
45
+ </th>
46
+ <th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
47
+ Start Date
48
+ </th>
49
+ <th className="px-6 py-4 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
50
+ Status
51
+ </th>
52
+ </tr>
53
+ </thead>
54
+ <tbody className="bg-white divide-y divide-gray-200">
55
+ {applications.length === 0 ? (
56
+ <tr>
57
+ <td colSpan={3} className="px-6 py-8 text-center text-gray-500">
58
+ No applications found
59
+ </td>
60
+ </tr>
61
+ ) : (
62
+ applications.map((application) => (
63
+ <tr
64
+ key={application.id}
65
+ onClick={() => onRowClick(application)}
66
+ className="hover:bg-gray-50 cursor-pointer transition-colors"
67
+ >
68
+ <td className="px-6 py-4 whitespace-nowrap">
69
+ <div className="flex items-center">
70
+ <div className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0">
71
+ <span className="text-sm font-medium text-purple-700">
72
+ {application.applicantName?.charAt(0) || "?"}
73
+ </span>
74
+ </div>
75
+ <div className="ml-4">
76
+ <div className="text-sm font-medium text-gray-900">
77
+ {application.applicantName || "Unknown"}
78
+ </div>
79
+ <div className="text-sm text-gray-500">
80
+ {application.propertyName || application.propertyAddress}
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </td>
85
+ <td className="px-6 py-4 whitespace-nowrap">
86
+ <div className="text-sm text-gray-900">
87
+ {formatDate(application.startDate || application.submittedDate)}
88
+ </div>
89
+ </td>
90
+ <td className="px-6 py-4 whitespace-nowrap">
91
+ <span
92
+ className={`inline-flex px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(application.status)}`}
93
+ >
94
+ {application.status}
95
+ </span>
96
+ </td>
97
+ </tr>
98
+ ))
99
+ )}
100
+ </tbody>
101
+ </table>
102
+ </div>
103
+ </Card>
104
+ );
105
+ };
@@ -0,0 +1,191 @@
1
+ import React, { useState } from "react";
2
+ import { X } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { PriorityBadge } from "./PriorityBadge.js";
5
+ import type { MaintenanceRequest } from "../lib/types.js";
6
+
7
+ interface MaintenanceDetailsModalProps {
8
+ request: MaintenanceRequest;
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ onSave: (requestId: string, status: string) => Promise<void>;
12
+ }
13
+
14
+ export const MaintenanceDetailsModal: React.FC<MaintenanceDetailsModalProps> = ({
15
+ request,
16
+ isOpen,
17
+ onClose,
18
+ onSave,
19
+ }) => {
20
+ const [selectedStatus, setSelectedStatus] = useState(request.status);
21
+ const [isSaving, setIsSaving] = useState(false);
22
+
23
+ // Determine if status is editable
24
+ // Only New and In Progress can be updated to Resolved
25
+ const isStatusEditable = request.status === "New" || request.status === "In Progress";
26
+
27
+ // Status options
28
+ const statusOptions = ["New", "In Progress", "Resolved"];
29
+
30
+ const handleSave = async () => {
31
+ if (!isStatusEditable) return;
32
+
33
+ setIsSaving(true);
34
+ try {
35
+ await onSave(request.id, selectedStatus);
36
+ onClose();
37
+ } catch (error) {
38
+ console.error("Error saving status:", error);
39
+ } finally {
40
+ setIsSaving(false);
41
+ }
42
+ };
43
+
44
+ if (!isOpen) return null;
45
+
46
+ return (
47
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
48
+ {/* Backdrop */}
49
+ <div className="fixed inset-0 bg-black bg-opacity-50" onClick={onClose} />
50
+
51
+ {/* Modal */}
52
+ <div className="relative bg-white rounded-lg shadow-xl w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
53
+ {/* Header */}
54
+ <div className="flex items-center justify-between p-6 border-b border-gray-200">
55
+ <h2 className="text-xl font-semibold text-gray-900">Maintenance Request Details</h2>
56
+ <button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
57
+ <X className="w-6 h-6" />
58
+ </button>
59
+ </div>
60
+
61
+ {/* Content */}
62
+ <div className="p-6 space-y-6">
63
+ {/* Image */}
64
+ {request.imageUrl && (
65
+ <div>
66
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
67
+ Image
68
+ </h3>
69
+ <div className="w-full h-64 rounded-lg bg-gray-200 overflow-hidden">
70
+ <img
71
+ src={request.imageUrl}
72
+ alt={request.description}
73
+ className="w-full h-full object-cover"
74
+ />
75
+ </div>
76
+ </div>
77
+ )}
78
+
79
+ {/* Description */}
80
+ <div>
81
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
82
+ Description
83
+ </h3>
84
+ <p className="text-lg font-medium text-gray-900">{request.description}</p>
85
+ </div>
86
+
87
+ {/* Issue Type & Priority */}
88
+ <div className="grid grid-cols-2 gap-4">
89
+ <div>
90
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
91
+ Issue Type
92
+ </h3>
93
+ <p className="text-base text-gray-900">{request.issueType}</p>
94
+ </div>
95
+ <div>
96
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
97
+ Priority
98
+ </h3>
99
+ <PriorityBadge priority={request.priority} />
100
+ </div>
101
+ </div>
102
+
103
+ {/* Property */}
104
+ <div>
105
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
106
+ Property
107
+ </h3>
108
+ <p className="text-base text-gray-900">{request.propertyAddress}</p>
109
+ </div>
110
+
111
+ {/* Tenant */}
112
+ <div>
113
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
114
+ Tenant
115
+ </h3>
116
+ <p className="text-base text-gray-900">{request.tenantName || "Unknown"}</p>
117
+ {request.tenantUnit && (
118
+ <p className="text-sm text-gray-600 mt-1">Unit: {request.tenantUnit}</p>
119
+ )}
120
+ </div>
121
+
122
+ {/* Assigned Worker */}
123
+ {request.assignedWorkerName && (
124
+ <div>
125
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
126
+ Assigned Worker
127
+ </h3>
128
+ <p className="text-base text-gray-900">{request.assignedWorkerName}</p>
129
+ {request.assignedWorkerOrg && (
130
+ <p className="text-sm text-gray-600 mt-1">{request.assignedWorkerOrg}</p>
131
+ )}
132
+ </div>
133
+ )}
134
+
135
+ {/* Scheduled Date */}
136
+ {request.formattedDate && (
137
+ <div>
138
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
139
+ Scheduled Date
140
+ </h3>
141
+ <p className="text-base text-gray-900">{request.formattedDate}</p>
142
+ </div>
143
+ )}
144
+
145
+ {/* Status */}
146
+ <div>
147
+ <h3 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
148
+ Status
149
+ </h3>
150
+ {isStatusEditable ? (
151
+ <select
152
+ value={selectedStatus}
153
+ onChange={(e) => setSelectedStatus(e.target.value)}
154
+ className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-purple-500 focus:border-transparent"
155
+ >
156
+ {statusOptions.map((status) => (
157
+ <option key={status} value={status}>
158
+ {status}
159
+ </option>
160
+ ))}
161
+ </select>
162
+ ) : (
163
+ <div className="flex items-center">
164
+ <span className="px-4 py-2 bg-gray-100 text-gray-700 rounded-md font-medium">
165
+ {request.status}
166
+ </span>
167
+ <span className="ml-3 text-sm text-gray-500">(Cannot be modified)</span>
168
+ </div>
169
+ )}
170
+ </div>
171
+ </div>
172
+
173
+ {/* Footer */}
174
+ <div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200">
175
+ <Button variant="outline" onClick={onClose} disabled={isSaving}>
176
+ Close
177
+ </Button>
178
+ {isStatusEditable && (
179
+ <Button
180
+ onClick={handleSave}
181
+ disabled={isSaving || selectedStatus === request.status}
182
+ className="bg-purple-600 hover:bg-purple-700"
183
+ >
184
+ {isSaving ? "Saving..." : "Save Changes"}
185
+ </Button>
186
+ )}
187
+ </div>
188
+ </div>
189
+ </div>
190
+ );
191
+ };
@@ -0,0 +1,274 @@
1
+ import React from "react";
2
+ import { X } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import type { Property } from "../lib/types.js";
5
+
6
+ interface PropertyDetailsModalProps {
7
+ property: Property;
8
+ isOpen: boolean;
9
+ onClose: () => void;
10
+ }
11
+
12
+ export const PropertyDetailsModal: React.FC<PropertyDetailsModalProps> = ({
13
+ property,
14
+ isOpen,
15
+ onClose,
16
+ }) => {
17
+ if (!isOpen) return null;
18
+
19
+ const formatCurrency = (amount: number) => {
20
+ return new Intl.NumberFormat("en-US", {
21
+ style: "currency",
22
+ currency: "USD",
23
+ minimumFractionDigits: 0,
24
+ }).format(amount);
25
+ };
26
+
27
+ const getStatusColor = (status: string) => {
28
+ switch (status.toLowerCase()) {
29
+ case "available":
30
+ return "bg-green-100 text-green-700";
31
+ case "rented":
32
+ return "bg-blue-100 text-blue-700";
33
+ case "maintenance":
34
+ return "bg-yellow-100 text-yellow-700";
35
+ default:
36
+ return "bg-gray-100 text-gray-700";
37
+ }
38
+ };
39
+
40
+ return (
41
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
42
+ {/* Backdrop */}
43
+ <div className="fixed inset-0 bg-black bg-opacity-50" onClick={onClose} />
44
+
45
+ {/* Modal */}
46
+ <div className="relative bg-white rounded-lg shadow-xl w-full max-w-4xl mx-4 max-h-[90vh] overflow-y-auto">
47
+ {/* Header */}
48
+ <div className="flex items-center justify-between p-6 border-b border-gray-200">
49
+ <h2 className="text-xl font-semibold text-gray-900">Property Details</h2>
50
+ <button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
51
+ <X className="w-6 h-6" />
52
+ </button>
53
+ </div>
54
+
55
+ {/* Content */}
56
+ <div className="p-6 space-y-6">
57
+ {/* Hero Image */}
58
+ {property.heroImage && (
59
+ <div className="w-full h-80 rounded-lg bg-gray-200 overflow-hidden">
60
+ <img
61
+ src={property.heroImage}
62
+ alt={property.name}
63
+ className="w-full h-full object-cover"
64
+ />
65
+ </div>
66
+ )}
67
+
68
+ {/* Property Name and Address */}
69
+ <div>
70
+ <h3 className="text-2xl font-bold text-gray-900">{property.name}</h3>
71
+ <p className="text-base text-gray-600 mt-1">{property.address}</p>
72
+ </div>
73
+
74
+ {/* Type, Status, and Rent */}
75
+ <div className="grid grid-cols-3 gap-4">
76
+ <div>
77
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
78
+ Type
79
+ </h4>
80
+ <p className="text-base text-gray-900 capitalize">{property.type}</p>
81
+ </div>
82
+ <div>
83
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
84
+ Status
85
+ </h4>
86
+ <span
87
+ className={`inline-flex px-3 py-1 rounded-full text-sm font-medium capitalize ${getStatusColor(property.status)}`}
88
+ >
89
+ {property.status}
90
+ </span>
91
+ </div>
92
+ <div>
93
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
94
+ Monthly Rent
95
+ </h4>
96
+ <p className="text-xl font-bold text-purple-700">
97
+ {formatCurrency(property.monthlyRent)}
98
+ </p>
99
+ </div>
100
+ </div>
101
+
102
+ {/* Property Specifications */}
103
+ <div>
104
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
105
+ Property Specifications
106
+ </h4>
107
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
108
+ {property.bedrooms !== undefined && (
109
+ <div className="bg-gray-50 rounded-lg p-4">
110
+ <p className="text-sm text-gray-600">Bedrooms</p>
111
+ <p className="text-lg font-semibold text-gray-900">{property.bedrooms}</p>
112
+ </div>
113
+ )}
114
+ {property.bathrooms !== undefined && (
115
+ <div className="bg-gray-50 rounded-lg p-4">
116
+ <p className="text-sm text-gray-600">Bathrooms</p>
117
+ <p className="text-lg font-semibold text-gray-900">{property.bathrooms}</p>
118
+ </div>
119
+ )}
120
+ {property.sqFt !== undefined && (
121
+ <div className="bg-gray-50 rounded-lg p-4">
122
+ <p className="text-sm text-gray-600">Square Feet</p>
123
+ <p className="text-lg font-semibold text-gray-900">
124
+ {property.sqFt.toLocaleString()}
125
+ </p>
126
+ </div>
127
+ )}
128
+ {property.yearBuilt !== undefined && (
129
+ <div className="bg-gray-50 rounded-lg p-4">
130
+ <p className="text-sm text-gray-600">Year Built</p>
131
+ <p className="text-lg font-semibold text-gray-900">{property.yearBuilt}</p>
132
+ </div>
133
+ )}
134
+ </div>
135
+ </div>
136
+
137
+ {/* Lease Information */}
138
+ <div>
139
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
140
+ Lease Information
141
+ </h4>
142
+ <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
143
+ {property.deposit !== undefined && (
144
+ <div>
145
+ <p className="text-sm text-gray-600">Security Deposit</p>
146
+ <p className="text-base font-semibold text-gray-900">
147
+ {formatCurrency(property.deposit)}
148
+ </p>
149
+ </div>
150
+ )}
151
+ {property.leaseTerm !== undefined && (
152
+ <div>
153
+ <p className="text-sm text-gray-600">Lease Term</p>
154
+ <p className="text-base font-semibold text-gray-900">
155
+ {property.leaseTerm} months
156
+ </p>
157
+ </div>
158
+ )}
159
+ {property.availableDate && (
160
+ <div>
161
+ <p className="text-sm text-gray-600">Available Date</p>
162
+ <p className="text-base font-semibold text-gray-900">{property.availableDate}</p>
163
+ </div>
164
+ )}
165
+ </div>
166
+ </div>
167
+
168
+ {/* Amenities */}
169
+ <div>
170
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
171
+ Amenities
172
+ </h4>
173
+ <div className="flex flex-wrap gap-4">
174
+ {property.parking !== undefined && (
175
+ <div className="flex items-center gap-2">
176
+ <span className="text-lg">🚗</span>
177
+ <span className="text-sm text-gray-700">{property.parking} Parking Space(s)</span>
178
+ </div>
179
+ )}
180
+ {property.petFriendly !== undefined && (
181
+ <div className="flex items-center gap-2">
182
+ <span className="text-lg">{property.petFriendly ? "🐾" : "🚫"}</span>
183
+ <span className="text-sm text-gray-700">
184
+ {property.petFriendly ? "Pet Friendly" : "No Pets"}
185
+ </span>
186
+ </div>
187
+ )}
188
+ </div>
189
+ </div>
190
+
191
+ {/* Features */}
192
+ {property.features && property.features.length > 0 && (
193
+ <div>
194
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
195
+ Features
196
+ </h4>
197
+ <div className="flex flex-wrap gap-2">
198
+ {property.features.map((feature, index) => (
199
+ <span
200
+ key={index}
201
+ className="px-3 py-1 bg-purple-100 text-purple-700 rounded-full text-sm font-medium"
202
+ >
203
+ {feature}
204
+ </span>
205
+ ))}
206
+ </div>
207
+ </div>
208
+ )}
209
+
210
+ {/* Utilities */}
211
+ {property.utilities && property.utilities.length > 0 && (
212
+ <div>
213
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-3">
214
+ Utilities Included
215
+ </h4>
216
+ <div className="flex flex-wrap gap-2">
217
+ {property.utilities.map((utility, index) => (
218
+ <span
219
+ key={index}
220
+ className="px-3 py-1 bg-blue-100 text-blue-700 rounded-full text-sm font-medium"
221
+ >
222
+ {utility}
223
+ </span>
224
+ ))}
225
+ </div>
226
+ </div>
227
+ )}
228
+
229
+ {/* Description */}
230
+ {property.description && (
231
+ <div>
232
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
233
+ Description
234
+ </h4>
235
+ <p className="text-sm text-gray-700 whitespace-pre-wrap">{property.description}</p>
236
+ </div>
237
+ )}
238
+
239
+ {/* Virtual Tour */}
240
+ {property.tourUrl && (
241
+ <div>
242
+ <h4 className="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">
243
+ Virtual Tour
244
+ </h4>
245
+ <a
246
+ href={property.tourUrl}
247
+ target="_blank"
248
+ rel="noopener noreferrer"
249
+ className="inline-flex items-center gap-2 text-purple-600 hover:text-purple-700 font-medium"
250
+ >
251
+ <span>View Virtual Tour</span>
252
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
253
+ <path
254
+ strokeLinecap="round"
255
+ strokeLinejoin="round"
256
+ strokeWidth={2}
257
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
258
+ />
259
+ </svg>
260
+ </a>
261
+ </div>
262
+ )}
263
+ </div>
264
+
265
+ {/* Footer */}
266
+ <div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200">
267
+ <Button variant="outline" onClick={onClose}>
268
+ Close
269
+ </Button>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ );
274
+ };
@@ -2,36 +2,36 @@ import React from "react";
2
2
  import { Check } from "lucide-react";
3
3
 
4
4
  interface StatusBadgeProps {
5
- status: "new" | "assigned" | "scheduled" | "in_progress" | "completed";
5
+ status: string;
6
6
  }
7
7
 
8
8
  export const StatusBadge: React.FC<StatusBadgeProps> = ({ status }) => {
9
- const styles = {
10
- new: "bg-pink-100 text-pink-700",
11
- assigned: "bg-purple-100 text-purple-700",
12
- scheduled: "bg-blue-100 text-blue-700",
13
- in_progress: "bg-yellow-100 text-yellow-700",
14
- completed: "bg-green-100 text-green-700",
9
+ const statusLower = status.toLowerCase();
10
+
11
+ const getStyle = () => {
12
+ if (statusLower === "new") return "bg-pink-100 text-pink-700";
13
+ if (statusLower === "in progress") return "bg-yellow-100 text-yellow-700";
14
+ if (statusLower === "resolved") return "bg-green-100 text-green-700";
15
+ return "bg-gray-100 text-gray-700";
15
16
  };
16
17
 
17
- const labels = {
18
- new: "Needs Action",
19
- assigned: "Assigned",
20
- scheduled: "Scheduled",
21
- in_progress: "In Progress",
22
- completed: "Completed",
18
+ const getLabel = () => {
19
+ if (statusLower === "new") return "Needs Action";
20
+ if (statusLower === "in progress") return "In Progress";
21
+ if (statusLower === "resolved") return "Resolved";
22
+ return status;
23
23
  };
24
24
 
25
- const showCheckmark = status === "completed";
26
- const showDot = status === "new" || status === "in_progress";
25
+ const showCheckmark = statusLower === "resolved";
26
+ const showDot = statusLower === "new" || statusLower === "in progress";
27
27
 
28
28
  return (
29
29
  <span
30
- className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium ${styles[status]}`}
30
+ className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-sm font-medium ${getStyle()}`}
31
31
  >
32
32
  {showCheckmark && <Check className="w-4 h-4" />}
33
33
  {showDot && <span className="w-2 h-2 rounded-full bg-current" />}
34
- {labels[status]}
34
+ {getLabel()}
35
35
  </span>
36
36
  );
37
37
  };
@@ -9,6 +9,7 @@ interface TopBarProps {
9
9
 
10
10
  export const TopBar: React.FC<TopBarProps> = ({ onMenuClick }) => {
11
11
  const [userName, setUserName] = useState<string>("User");
12
+ const [showNotifications, setShowNotifications] = useState(false);
12
13
 
13
14
  useEffect(() => {
14
15
  const loadUserInfo = async () => {
@@ -19,6 +20,14 @@ export const TopBar: React.FC<TopBarProps> = ({ onMenuClick }) => {
19
20
  };
20
21
  loadUserInfo();
21
22
  }, []);
23
+
24
+ const handleNotificationClick = () => {
25
+ setShowNotifications(!showNotifications);
26
+ };
27
+
28
+ const handleCloseNotifications = () => {
29
+ setShowNotifications(false);
30
+ };
22
31
  return (
23
32
  <div className="bg-[#372949] text-white h-16 flex items-center justify-between px-6">
24
33
  {/* Left section - Logo and Menu */}
@@ -50,13 +59,34 @@ export const TopBar: React.FC<TopBarProps> = ({ onMenuClick }) => {
50
59
  </button>
51
60
 
52
61
  {/* Notifications */}
53
- <button
54
- className="p-2 hover:bg-purple-700 rounded-md transition-colors relative"
55
- aria-label="Notifications"
56
- >
57
- <Bell className="w-5 h-5" />
58
- <span className="absolute top-1 right-1 w-2 h-2 bg-pink-500 rounded-full"></span>
59
- </button>
62
+ <div className="relative">
63
+ <button
64
+ onClick={handleNotificationClick}
65
+ className="p-2 hover:bg-purple-700 rounded-md transition-colors relative"
66
+ aria-label="Notifications"
67
+ >
68
+ <Bell className="w-5 h-5" />
69
+ </button>
70
+
71
+ {/* Notifications Overlay */}
72
+ {showNotifications && (
73
+ <>
74
+ {/* Backdrop */}
75
+ <div className="fixed inset-0 z-40" onClick={handleCloseNotifications} />
76
+
77
+ {/* Notification Panel */}
78
+ <div className="absolute right-0 top-full mt-2 w-80 bg-white rounded-lg shadow-xl z-50 overflow-hidden">
79
+ <div className="p-4 border-b border-gray-200">
80
+ <h3 className="text-sm font-semibold text-gray-900">Notifications</h3>
81
+ </div>
82
+ <div className="p-8 text-center">
83
+ <Bell className="w-12 h-12 mx-auto text-gray-300 mb-3" />
84
+ <p className="text-sm text-gray-500">No new notifications</p>
85
+ </div>
86
+ </div>
87
+ </>
88
+ )}
89
+ </div>
60
90
 
61
91
  {/* User Profile */}
62
92
  <button className="flex items-center gap-2 px-3 py-2 hover:bg-purple-700 rounded-md transition-colors">