braid-ui 1.0.97 → 1.0.99
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.
- package/README.md +44 -327
- package/components.json +20 -0
- package/eslint.config.js +29 -0
- package/index.html +24 -0
- package/package.json +55 -115
- package/postcss.config.js +6 -0
- package/public/favicon.ico +0 -0
- package/public/placeholder.svg +1 -0
- package/public/robots.txt +14 -0
- package/src/App.css +42 -0
- package/src/App.tsx +94 -0
- package/src/components/MainLayout.tsx +15 -0
- package/src/components/alerts/AlertDocuments.tsx +320 -0
- package/src/components/alerts/AlertNotes.tsx +185 -0
- package/src/components/alerts/AlertTimeline.tsx +79 -0
- package/src/components/alerts/ContextSection.tsx +155 -0
- package/src/components/app-sidebar.tsx +341 -0
- package/src/components/form-sections/ACHBankCard.tsx +78 -0
- package/src/components/form-sections/ACHBasicInfoCard.tsx +100 -0
- package/src/components/form-sections/ACHTransferSection.tsx +64 -0
- package/src/components/form-sections/AddressForm.tsx +94 -0
- package/src/components/form-sections/BankAddressCard.tsx +95 -0
- package/src/components/form-sections/BankingDetailsCard.tsx +46 -0
- package/src/components/form-sections/BasicInfoCard.tsx +103 -0
- package/src/components/form-sections/BasicInfoSection.tsx +34 -0
- package/src/components/form-sections/BeneficiaryAddress.tsx +19 -0
- package/src/components/form-sections/BeneficiaryCard.tsx +41 -0
- package/src/components/form-sections/BeneficiaryDomesticWire.tsx +23 -0
- package/src/components/form-sections/BusinessProfileCard.tsx +131 -0
- package/src/components/form-sections/BusinessStatusCard.tsx +53 -0
- package/src/components/form-sections/ContactInfoCard.tsx +63 -0
- package/src/components/form-sections/CounterpartyBasicInfo.tsx +101 -0
- package/src/components/form-sections/CounterpartyProfileCard.tsx +104 -0
- package/src/components/form-sections/CounterpartyRecordsCard.tsx +41 -0
- package/src/components/form-sections/IntermediaryCard.tsx +77 -0
- package/src/components/form-sections/IntermediaryFI.tsx +41 -0
- package/src/components/form-sections/IntermediaryFIAddress.tsx +14 -0
- package/src/components/form-sections/OriginatorCard.tsx +49 -0
- package/src/components/form-sections/OriginatorFI.tsx +42 -0
- package/src/components/form-sections/OriginatorFIAddress.tsx +14 -0
- package/src/components/form-sections/PaymentInformationSection.tsx +163 -0
- package/src/components/form-sections/ReceiverCard.tsx +94 -0
- package/src/components/form-sections/WireTransferSection.tsx +75 -0
- package/src/components/layouts/list-page.tsx +103 -0
- package/src/components/transaction/ACHDetailsSection.tsx +95 -0
- package/src/components/transaction/WireDetailsSection.tsx +112 -0
- package/src/components/ui/account-card.tsx +94 -0
- package/src/components/ui/badge.tsx +75 -0
- package/src/components/ui/breadcrumb.tsx +78 -0
- package/src/components/ui/business-type-badge.tsx +42 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/calendar.tsx +49 -0
- package/src/components/ui/card.tsx +223 -0
- package/src/components/ui/container.tsx +45 -0
- package/src/components/ui/counterparty-type-badge.tsx +53 -0
- package/src/components/ui/data-grid.tsx +99 -0
- package/src/components/ui/data-table.tsx +152 -0
- package/src/components/ui/detail-page-layout.tsx +83 -0
- package/src/components/ui/dialog.tsx +120 -0
- package/src/components/ui/dropdown-menu.tsx +82 -0
- package/src/components/ui/editable-form-card.tsx +106 -0
- package/src/components/ui/editable-info-field.tsx +67 -0
- package/src/components/ui/enhanced-input.tsx +78 -0
- package/src/components/ui/enhanced-select.tsx +101 -0
- package/src/components/ui/enhanced-textarea.tsx +64 -0
- package/src/components/ui/entity-card.tsx +140 -0
- package/src/components/ui/form-card.tsx +40 -0
- package/src/components/ui/form-field.tsx +50 -0
- package/src/components/ui/form-input.tsx +29 -0
- package/src/components/ui/form-provider.tsx +18 -0
- package/src/components/ui/form-section.tsx +66 -0
- package/src/components/ui/form-select.tsx +35 -0
- package/src/components/ui/info-field.tsx +36 -0
- package/src/components/ui/json-viewer.tsx +146 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/metric-card.tsx +80 -0
- package/src/components/ui/page-layout.tsx +183 -0
- package/src/components/ui/popover.tsx +29 -0
- package/src/components/ui/responsive-grid.tsx +46 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/sidebar.tsx +775 -0
- package/src/components/ui/sonner.tsx +29 -0
- package/src/components/ui/stack.tsx +77 -0
- package/src/components/ui/status-badge.tsx +68 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/ui/toast.tsx +127 -0
- package/src/components/ui/toaster.tsx +33 -0
- package/src/components/ui/tooltip.tsx +28 -0
- package/src/components/ui/use-toast.ts +3 -0
- package/src/components/ui-kit/dashboard-demo.tsx +156 -0
- package/src/components/ui-kit/pattern-library.tsx +248 -0
- package/src/components/ui-kit/showcase.tsx +211 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-toast.ts +191 -0
- package/src/hooks/useEditState.ts +70 -0
- package/src/hooks/useFormWithEditState.ts +115 -0
- package/src/{styles.css → index.css} +10 -4
- package/src/lib/constants.ts +25 -0
- package/src/lib/mock-data/alert-data.ts +275 -0
- package/src/lib/mock-data/banking-data.ts +72 -0
- package/src/lib/mock-data/business-data.ts +71 -0
- package/src/lib/mock-data/counterparty-data.ts +70 -0
- package/src/lib/mock-data/index.ts +5 -0
- package/src/lib/mock-data/transaction-data.ts +283 -0
- package/src/lib/mock-data/wire-data.ts +103 -0
- package/src/lib/mock-data.tsx +180 -0
- package/src/lib/schemas/banking-schemas.ts +30 -0
- package/src/lib/schemas/business-schemas.ts +36 -0
- package/src/lib/schemas/counterparty-schemas.ts +43 -0
- package/src/lib/schemas/index.ts +5 -0
- package/src/lib/schemas/wire-schemas.ts +44 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/src/pages/Cases.tsx +16 -0
- package/src/pages/Dashboard.tsx +16 -0
- package/src/pages/NotFound.tsx +27 -0
- package/src/pages/TransactionHistory.tsx +532 -0
- package/src/pages/UIKit.tsx +51 -0
- package/src/pages/alerts/AlertDetail.tsx +193 -0
- package/src/pages/alerts/Alerts.tsx +373 -0
- package/src/pages/business/Business.tsx +48 -0
- package/src/pages/business/Create.tsx +173 -0
- package/src/pages/counterparty/Create.tsx +48 -0
- package/src/pages/counterparty/DomesticWire.tsx +78 -0
- package/src/pages/counterparty/Manage.tsx +79 -0
- package/src/pages/transactions/NewTransaction.tsx +527 -0
- package/src/pages/transactions/TransactionDetail.tsx +192 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.ts +124 -0
- package/tsconfig.app.json +30 -0
- package/tsconfig.json +19 -0
- package/tsconfig.node.json +22 -0
- package/vite.config.ts +22 -0
- package/dist/css/braid-ui-variables.css +0 -88
- package/dist/css/braid-ui.css +0 -4484
- package/dist/css/braid-ui.min.css +0 -1
- package/dist/index.cjs +0 -4
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2429
- package/dist/index.d.ts +0 -2429
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -1
- package/src/styles-only.css +0 -121
- /package/{dist/braid-logo-343BOQZ2.png → src/assets/braid-logo.png} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
package/src/App.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#root {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
will-change: filter;
|
|
12
|
+
transition: filter 300ms;
|
|
13
|
+
}
|
|
14
|
+
.logo:hover {
|
|
15
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
16
|
+
}
|
|
17
|
+
.logo.react:hover {
|
|
18
|
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes logo-spin {
|
|
22
|
+
from {
|
|
23
|
+
transform: rotate(0deg);
|
|
24
|
+
}
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
a:nth-of-type(2) .logo {
|
|
32
|
+
animation: logo-spin infinite 20s linear;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card {
|
|
37
|
+
padding: 2em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.read-the-docs {
|
|
41
|
+
color: #888;
|
|
42
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Toaster } from "@/components/ui/toaster";
|
|
2
|
+
import { Toaster as Sonner } from "@/components/ui/sonner";
|
|
3
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
+
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
|
6
|
+
import { SidebarProvider } from "@/components/ui/sidebar";
|
|
7
|
+
import { AppSidebar } from "@/components/app-sidebar";
|
|
8
|
+
import { MainLayout } from "@/components/MainLayout";
|
|
9
|
+
import NotFound from "./pages/NotFound";
|
|
10
|
+
|
|
11
|
+
// Counterparty pages
|
|
12
|
+
import CounterpartyManage from "./pages/counterparty/Manage";
|
|
13
|
+
import CreateCounterparty from "./pages/counterparty/Create";
|
|
14
|
+
import CounterpartyDomesticWire from "./pages/counterparty/DomesticWire";
|
|
15
|
+
|
|
16
|
+
// Alerts pages
|
|
17
|
+
import Alerts from "./pages/alerts/Alerts";
|
|
18
|
+
import AlertDetail from "./pages/alerts/AlertDetail";
|
|
19
|
+
|
|
20
|
+
// Dashboard page
|
|
21
|
+
import Dashboard from "./pages/Dashboard";
|
|
22
|
+
|
|
23
|
+
// Cases page
|
|
24
|
+
import Cases from "./pages/Cases";
|
|
25
|
+
|
|
26
|
+
// Business pages
|
|
27
|
+
import Business from "./pages/business/Business";
|
|
28
|
+
import CreateBusiness from "./pages/business/Create";
|
|
29
|
+
|
|
30
|
+
// UI Kit pages
|
|
31
|
+
import UIKit from "./pages/UIKit";
|
|
32
|
+
|
|
33
|
+
// Transaction pages
|
|
34
|
+
import TransactionHistory from "./pages/TransactionHistory";
|
|
35
|
+
import TransactionDetail from "./pages/transactions/TransactionDetail";
|
|
36
|
+
import NewTransaction from "./pages/transactions/NewTransaction";
|
|
37
|
+
|
|
38
|
+
const queryClient = new QueryClient();
|
|
39
|
+
|
|
40
|
+
const App = () => (
|
|
41
|
+
<QueryClientProvider client={queryClient}>
|
|
42
|
+
<TooltipProvider>
|
|
43
|
+
<Toaster />
|
|
44
|
+
<Sonner />
|
|
45
|
+
<BrowserRouter>
|
|
46
|
+
<SidebarProvider>
|
|
47
|
+
<div className="flex min-h-screen w-full">
|
|
48
|
+
<AppSidebar />
|
|
49
|
+
<MainLayout>
|
|
50
|
+
<Routes>
|
|
51
|
+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
52
|
+
|
|
53
|
+
{/* Dashboard Route */}
|
|
54
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
55
|
+
|
|
56
|
+
{/* Alerts Routes */}
|
|
57
|
+
<Route path="/alerts" element={<Alerts />} />
|
|
58
|
+
<Route path="/alerts/:id" element={<AlertDetail />} />
|
|
59
|
+
<Route path="/cases" element={<Cases />} />
|
|
60
|
+
|
|
61
|
+
{/* Counterparty Routes */}
|
|
62
|
+
<Route path="/counterparty/manage" element={<CounterpartyManage />} />
|
|
63
|
+
<Route path="/counterparty/create" element={<CreateCounterparty />} />
|
|
64
|
+
<Route path="/counterparty/domestic-wire" element={<CounterpartyDomesticWire />} />
|
|
65
|
+
|
|
66
|
+
{/* Legacy redirects */}
|
|
67
|
+
<Route path="/counterparty/details" element={<Navigate to="/counterparty/manage" replace />} />
|
|
68
|
+
<Route path="/counterparty/ach" element={<Navigate to="/counterparty/manage" replace />} />
|
|
69
|
+
<Route path="/counterparty/wire" element={<Navigate to="/counterparty/manage" replace />} />
|
|
70
|
+
|
|
71
|
+
{/* Business Routes */}
|
|
72
|
+
<Route path="/business" element={<Business />} />
|
|
73
|
+
<Route path="/business/create" element={<CreateBusiness />} />
|
|
74
|
+
|
|
75
|
+
{/* UI Kit Routes */}
|
|
76
|
+
<Route path="/ui-kit" element={<UIKit />} />
|
|
77
|
+
|
|
78
|
+
{/* Transaction Routes */}
|
|
79
|
+
<Route path="/transactions/history" element={<TransactionHistory />} />
|
|
80
|
+
<Route path="/transactions/new" element={<NewTransaction />} />
|
|
81
|
+
<Route path="/transactions/:id" element={<TransactionDetail />} />
|
|
82
|
+
|
|
83
|
+
{/* Catch-all route */}
|
|
84
|
+
<Route path="*" element={<NotFound />} />
|
|
85
|
+
</Routes>
|
|
86
|
+
</MainLayout>
|
|
87
|
+
</div>
|
|
88
|
+
</SidebarProvider>
|
|
89
|
+
</BrowserRouter>
|
|
90
|
+
</TooltipProvider>
|
|
91
|
+
</QueryClientProvider>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export default App;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
interface MainLayoutProps {
|
|
4
|
+
children: ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const MainLayout = ({ children }: MainLayoutProps) => {
|
|
8
|
+
return (
|
|
9
|
+
<div className="flex min-h-screen w-full">
|
|
10
|
+
<main className="flex-1 pl-4">
|
|
11
|
+
{children}
|
|
12
|
+
</main>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { createPortal } from "react-dom"
|
|
3
|
+
import { AlertDocument } from "@/lib/mock-data/alert-data"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import { toast } from "@/hooks/use-toast"
|
|
6
|
+
import { FileText, Upload, Download, Eye, Trash2, X } from "lucide-react"
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
import { EnhancedInput } from "@/components/ui/enhanced-input"
|
|
9
|
+
import { EnhancedTextarea } from "@/components/ui/enhanced-textarea"
|
|
10
|
+
import { EnhancedSelect } from "@/components/ui/enhanced-select"
|
|
11
|
+
|
|
12
|
+
interface AlertDocumentsProps {
|
|
13
|
+
alertId: string
|
|
14
|
+
documents: AlertDocument[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const AlertDocuments = ({ alertId, documents }: AlertDocumentsProps) => {
|
|
18
|
+
const [isUploading, setIsUploading] = useState(false)
|
|
19
|
+
const [showUploadDialog, setShowUploadDialog] = useState(false)
|
|
20
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
|
21
|
+
const [documentName, setDocumentName] = useState("")
|
|
22
|
+
const [description, setDescription] = useState("")
|
|
23
|
+
const [documentType, setDocumentType] = useState("")
|
|
24
|
+
|
|
25
|
+
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
26
|
+
const file = e.target.files?.[0]
|
|
27
|
+
if (!file) return
|
|
28
|
+
|
|
29
|
+
setSelectedFile(file)
|
|
30
|
+
setDocumentName(file.name)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handleUploadSubmit = () => {
|
|
34
|
+
if (!selectedFile || !documentName || !documentType) {
|
|
35
|
+
toast({
|
|
36
|
+
title: "Missing Information",
|
|
37
|
+
description: "Please fill in all required fields",
|
|
38
|
+
variant: "destructive"
|
|
39
|
+
})
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setIsUploading(true)
|
|
44
|
+
|
|
45
|
+
// Simulate upload
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
const newDocument: AlertDocument = {
|
|
48
|
+
id: String(Date.now()),
|
|
49
|
+
name: documentName,
|
|
50
|
+
type: documentType,
|
|
51
|
+
description: description || undefined,
|
|
52
|
+
size: `${(selectedFile.size / 1024 / 1024).toFixed(1)} MB`,
|
|
53
|
+
uploadedBy: "Current User",
|
|
54
|
+
uploadedAt: new Date().toISOString()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
toast({
|
|
58
|
+
title: "Upload Successful",
|
|
59
|
+
description: `${documentName} uploaded successfully`
|
|
60
|
+
})
|
|
61
|
+
setIsUploading(false)
|
|
62
|
+
setShowUploadDialog(false)
|
|
63
|
+
resetForm()
|
|
64
|
+
}, 1000)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const resetForm = () => {
|
|
68
|
+
setSelectedFile(null)
|
|
69
|
+
setDocumentName("")
|
|
70
|
+
setDescription("")
|
|
71
|
+
setDocumentType("")
|
|
72
|
+
const fileInput = document.getElementById("file-upload") as HTMLInputElement
|
|
73
|
+
if (fileInput) fileInput.value = ""
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const handleCancelUpload = () => {
|
|
77
|
+
setShowUploadDialog(false)
|
|
78
|
+
resetForm()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const isImageType = (name: string) => {
|
|
82
|
+
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']
|
|
83
|
+
return imageExtensions.some(ext => name.toLowerCase().endsWith(ext))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const getFileIcon = (type: string) => {
|
|
87
|
+
return <FileText className="h-5 w-5" />
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const getFileTypeColor = (type: string) => {
|
|
91
|
+
switch (type.toUpperCase()) {
|
|
92
|
+
case "PDF":
|
|
93
|
+
return "text-red-500"
|
|
94
|
+
case "DOCX":
|
|
95
|
+
case "DOC":
|
|
96
|
+
return "text-blue-500"
|
|
97
|
+
case "XLSX":
|
|
98
|
+
case "XLS":
|
|
99
|
+
return "text-green-500"
|
|
100
|
+
case "JPG":
|
|
101
|
+
case "JPEG":
|
|
102
|
+
case "PNG":
|
|
103
|
+
return "text-purple-500"
|
|
104
|
+
default:
|
|
105
|
+
return "text-muted-foreground"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const documentTypeOptions = [
|
|
110
|
+
{ value: "ID_DOCUMENT_FRONT", label: "ID Document (Front)" },
|
|
111
|
+
{ value: "ID_DOCUMENT_BACK", label: "ID Document (Back)" },
|
|
112
|
+
{ value: "PASSPORT", label: "Passport" },
|
|
113
|
+
{ value: "PROOF_OF_ADDRESS", label: "Proof of Address" },
|
|
114
|
+
{ value: "BANK_STATEMENT", label: "Bank Statement" },
|
|
115
|
+
{ value: "BUSINESS_LICENSE", label: "Business License" },
|
|
116
|
+
{ value: "TAX_DOCUMENT", label: "Tax Document" },
|
|
117
|
+
{ value: "OTHER", label: "Other" }
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="space-y-6">
|
|
122
|
+
{/* Upload Dialog - Render using portal */}
|
|
123
|
+
{showUploadDialog && createPortal(
|
|
124
|
+
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
|
125
|
+
<div
|
|
126
|
+
className="fixed inset-0 bg-background/80 backdrop-blur-sm"
|
|
127
|
+
onClick={handleCancelUpload}
|
|
128
|
+
/>
|
|
129
|
+
<div className="relative bg-card border rounded-lg shadow-lg max-w-lg w-full p-6 z-[101]">
|
|
130
|
+
<div className="flex items-center justify-between mb-6">
|
|
131
|
+
<h2 className="text-2xl font-bold">Upload Document</h2>
|
|
132
|
+
<Button
|
|
133
|
+
variant="ghost"
|
|
134
|
+
size="sm"
|
|
135
|
+
className="h-8 w-8 p-0"
|
|
136
|
+
onClick={handleCancelUpload}
|
|
137
|
+
>
|
|
138
|
+
<X className="h-4 w-4" />
|
|
139
|
+
</Button>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div className="space-y-4">
|
|
143
|
+
{/* File Upload Area Inside Dialog */}
|
|
144
|
+
<div className="border-2 border-dashed rounded-lg p-6 text-center hover:border-primary/50 transition-colors">
|
|
145
|
+
<input
|
|
146
|
+
type="file"
|
|
147
|
+
id="file-upload-dialog"
|
|
148
|
+
className="hidden"
|
|
149
|
+
onChange={handleFileSelect}
|
|
150
|
+
disabled={isUploading}
|
|
151
|
+
/>
|
|
152
|
+
<label
|
|
153
|
+
htmlFor="file-upload-dialog"
|
|
154
|
+
className={cn(
|
|
155
|
+
"cursor-pointer flex flex-col items-center gap-2",
|
|
156
|
+
isUploading && "opacity-50 cursor-not-allowed"
|
|
157
|
+
)}
|
|
158
|
+
>
|
|
159
|
+
<Upload className="h-8 w-8 text-muted-foreground" />
|
|
160
|
+
<div>
|
|
161
|
+
<p className="text-sm font-medium text-foreground">
|
|
162
|
+
{selectedFile ? "Change file" : "Click to upload or drag and drop"}
|
|
163
|
+
</p>
|
|
164
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
165
|
+
PDF, DOC, DOCX, JPG, PNG up to 10MB
|
|
166
|
+
</p>
|
|
167
|
+
</div>
|
|
168
|
+
</label>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{selectedFile && (
|
|
172
|
+
<div className="p-3 bg-muted/50 rounded-lg">
|
|
173
|
+
<p className="text-sm text-muted-foreground">Selected file:</p>
|
|
174
|
+
<p className="text-sm font-medium">{selectedFile.name}</p>
|
|
175
|
+
<p className="text-xs text-muted-foreground">
|
|
176
|
+
{(selectedFile.size / 1024 / 1024).toFixed(2)} MB
|
|
177
|
+
</p>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
<EnhancedInput
|
|
182
|
+
label="Document name"
|
|
183
|
+
value={documentName}
|
|
184
|
+
onChange={(e) => setDocumentName(e.target.value)}
|
|
185
|
+
placeholder="Enter document name"
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
<EnhancedTextarea
|
|
189
|
+
label="Description"
|
|
190
|
+
value={description}
|
|
191
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
192
|
+
placeholder="Enter document description (optional)"
|
|
193
|
+
rows={3}
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
<EnhancedSelect
|
|
197
|
+
label="Document type"
|
|
198
|
+
value={documentType}
|
|
199
|
+
onValueChange={setDocumentType}
|
|
200
|
+
placeholder="Select document type"
|
|
201
|
+
options={documentTypeOptions}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="flex gap-3 mt-6">
|
|
206
|
+
<Button
|
|
207
|
+
variant="outline"
|
|
208
|
+
onClick={handleCancelUpload}
|
|
209
|
+
disabled={isUploading}
|
|
210
|
+
className="flex-1"
|
|
211
|
+
>
|
|
212
|
+
Cancel
|
|
213
|
+
</Button>
|
|
214
|
+
<Button
|
|
215
|
+
onClick={handleUploadSubmit}
|
|
216
|
+
disabled={isUploading}
|
|
217
|
+
className="flex-1"
|
|
218
|
+
>
|
|
219
|
+
{isUploading ? "Uploading..." : "Upload"}
|
|
220
|
+
</Button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>,
|
|
224
|
+
document.body
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{/* Documents List */}
|
|
228
|
+
<div className="space-y-3">
|
|
229
|
+
<div className="flex items-center justify-between">
|
|
230
|
+
<h3 className="text-sm font-medium text-foreground">Uploaded Documents</h3>
|
|
231
|
+
<Button
|
|
232
|
+
onClick={() => setShowUploadDialog(true)}
|
|
233
|
+
variant="ghost"
|
|
234
|
+
size="sm"
|
|
235
|
+
className="gap-2 text-primary hover:text-primary"
|
|
236
|
+
>
|
|
237
|
+
<Upload className="h-4 w-4" />
|
|
238
|
+
Upload
|
|
239
|
+
</Button>
|
|
240
|
+
</div>
|
|
241
|
+
{documents.length === 0 ? (
|
|
242
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
243
|
+
<FileText className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
244
|
+
<p>No documents uploaded yet</p>
|
|
245
|
+
</div>
|
|
246
|
+
) : (
|
|
247
|
+
<div className="space-y-2">
|
|
248
|
+
{documents.map((doc) => (
|
|
249
|
+
<div
|
|
250
|
+
key={doc.id}
|
|
251
|
+
className="border rounded-lg hover:bg-muted/30 transition-colors overflow-hidden"
|
|
252
|
+
>
|
|
253
|
+
{/* Image Preview for image types */}
|
|
254
|
+
{isImageType(doc.name) && doc.url && (
|
|
255
|
+
<div className="w-full bg-muted/50">
|
|
256
|
+
<img
|
|
257
|
+
src={doc.url}
|
|
258
|
+
alt={doc.name}
|
|
259
|
+
className="w-full h-48 object-cover"
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
)}
|
|
263
|
+
|
|
264
|
+
<div className="flex items-center justify-between p-3">
|
|
265
|
+
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
266
|
+
{!isImageType(doc.name) && (
|
|
267
|
+
<div className={cn(getFileTypeColor(doc.type))}>
|
|
268
|
+
{getFileIcon(doc.type)}
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
<div className="flex-1 min-w-0">
|
|
272
|
+
<div className="flex items-center gap-2">
|
|
273
|
+
<h4 className="font-medium text-sm truncate">{doc.name}</h4>
|
|
274
|
+
<span className={cn("text-xs px-2 py-0.5 rounded-full bg-muted", getFileTypeColor(doc.type))}>
|
|
275
|
+
{doc.type}
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
{doc.description && (
|
|
279
|
+
<p className="text-sm text-foreground mt-1">{doc.description}</p>
|
|
280
|
+
)}
|
|
281
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
282
|
+
{doc.size} • Uploaded by {doc.uploadedBy} • {new Date(doc.uploadedAt).toLocaleDateString()}
|
|
283
|
+
</p>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
<div className="flex items-center gap-1">
|
|
287
|
+
<Button
|
|
288
|
+
variant="ghost"
|
|
289
|
+
size="sm"
|
|
290
|
+
className="h-8 w-8 p-0"
|
|
291
|
+
onClick={() => toast({ title: "Preview", description: `Viewing ${doc.name}` })}
|
|
292
|
+
>
|
|
293
|
+
<Eye className="h-4 w-4" />
|
|
294
|
+
</Button>
|
|
295
|
+
<Button
|
|
296
|
+
variant="ghost"
|
|
297
|
+
size="sm"
|
|
298
|
+
className="h-8 w-8 p-0"
|
|
299
|
+
onClick={() => toast({ title: "Download", description: `Downloading ${doc.name}` })}
|
|
300
|
+
>
|
|
301
|
+
<Download className="h-4 w-4" />
|
|
302
|
+
</Button>
|
|
303
|
+
<Button
|
|
304
|
+
variant="ghost"
|
|
305
|
+
size="sm"
|
|
306
|
+
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
|
307
|
+
onClick={() => toast({ title: "Delete", description: `Deleted ${doc.name}` })}
|
|
308
|
+
>
|
|
309
|
+
<Trash2 className="h-4 w-4" />
|
|
310
|
+
</Button>
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
))}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { createPortal } from "react-dom"
|
|
3
|
+
import { AlertNote } from "@/lib/mock-data/alert-data"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import { EnhancedTextarea } from "@/components/ui/enhanced-textarea"
|
|
6
|
+
import { EnhancedSelect } from "@/components/ui/enhanced-select"
|
|
7
|
+
import { Badge } from "@/components/ui/badge"
|
|
8
|
+
import { toast } from "@/hooks/use-toast"
|
|
9
|
+
import { MessageSquare, Plus, X, Trash2 } from "lucide-react"
|
|
10
|
+
|
|
11
|
+
const NOTE_TYPE_OPTIONS = [
|
|
12
|
+
{ value: "RFI Note", label: "RFI Note" },
|
|
13
|
+
{ value: "Internal Note", label: "Internal Note" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
interface AlertNotesProps {
|
|
17
|
+
alertId: string
|
|
18
|
+
notes: AlertNote[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const AlertNotes = ({ alertId, notes }: AlertNotesProps) => {
|
|
22
|
+
const [showNoteDialog, setShowNoteDialog] = useState(false)
|
|
23
|
+
const [newNote, setNewNote] = useState("")
|
|
24
|
+
const [noteType, setNoteType] = useState<"RFI Note" | "Internal Note">("RFI Note")
|
|
25
|
+
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
26
|
+
|
|
27
|
+
const resetForm = () => {
|
|
28
|
+
setNewNote("")
|
|
29
|
+
setNoteType("RFI Note")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const handleCancelNote = () => {
|
|
33
|
+
setShowNoteDialog(false)
|
|
34
|
+
resetForm()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleAddNote = async () => {
|
|
38
|
+
if (!newNote.trim()) {
|
|
39
|
+
toast({
|
|
40
|
+
title: "Error",
|
|
41
|
+
description: "Please enter a note before submitting",
|
|
42
|
+
variant: "destructive"
|
|
43
|
+
})
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setIsSubmitting(true)
|
|
48
|
+
|
|
49
|
+
// Simulate API call
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
toast({
|
|
52
|
+
title: "Note Added",
|
|
53
|
+
description: "Your note has been added successfully"
|
|
54
|
+
})
|
|
55
|
+
setShowNoteDialog(false)
|
|
56
|
+
resetForm()
|
|
57
|
+
setIsSubmitting(false)
|
|
58
|
+
}, 500)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handleDeleteNote = (noteId: string, noteContent: string) => {
|
|
62
|
+
toast({
|
|
63
|
+
title: "Note Deleted",
|
|
64
|
+
description: `Note deleted successfully`
|
|
65
|
+
})
|
|
66
|
+
// TODO: API call to delete note
|
|
67
|
+
console.log("Deleting note:", noteId)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="space-y-6">
|
|
72
|
+
{/* Add Note Dialog - Render using portal */}
|
|
73
|
+
{showNoteDialog && createPortal(
|
|
74
|
+
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
|
|
75
|
+
<div
|
|
76
|
+
className="fixed inset-0 bg-background/80 backdrop-blur-sm"
|
|
77
|
+
onClick={handleCancelNote}
|
|
78
|
+
/>
|
|
79
|
+
<div className="relative bg-card border rounded-lg shadow-lg max-w-lg w-full p-6 z-[101]">
|
|
80
|
+
<div className="flex items-center justify-between mb-6">
|
|
81
|
+
<h2 className="text-2xl font-bold">Add Note</h2>
|
|
82
|
+
<Button
|
|
83
|
+
variant="ghost"
|
|
84
|
+
size="sm"
|
|
85
|
+
className="h-8 w-8 p-0"
|
|
86
|
+
onClick={handleCancelNote}
|
|
87
|
+
>
|
|
88
|
+
<X className="h-4 w-4" />
|
|
89
|
+
</Button>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="space-y-4">
|
|
93
|
+
<EnhancedSelect
|
|
94
|
+
label="Note Type"
|
|
95
|
+
value={noteType}
|
|
96
|
+
onValueChange={(value) => setNoteType(value as "RFI Note" | "Internal Note")}
|
|
97
|
+
options={NOTE_TYPE_OPTIONS}
|
|
98
|
+
/>
|
|
99
|
+
<EnhancedTextarea
|
|
100
|
+
label="Note"
|
|
101
|
+
placeholder="Enter your note here..."
|
|
102
|
+
value={newNote}
|
|
103
|
+
onChange={(e) => setNewNote(e.target.value)}
|
|
104
|
+
rows={6}
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div className="flex gap-3 mt-6">
|
|
109
|
+
<Button
|
|
110
|
+
variant="outline"
|
|
111
|
+
onClick={handleCancelNote}
|
|
112
|
+
disabled={isSubmitting}
|
|
113
|
+
className="flex-1"
|
|
114
|
+
>
|
|
115
|
+
Cancel
|
|
116
|
+
</Button>
|
|
117
|
+
<Button
|
|
118
|
+
onClick={handleAddNote}
|
|
119
|
+
disabled={isSubmitting || !newNote.trim()}
|
|
120
|
+
className="flex-1"
|
|
121
|
+
>
|
|
122
|
+
{isSubmitting ? "Adding..." : "Add Note"}
|
|
123
|
+
</Button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>,
|
|
127
|
+
document.body
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{/* Notes History */}
|
|
131
|
+
<div className="space-y-4">
|
|
132
|
+
<div className="flex items-center justify-between">
|
|
133
|
+
<h3 className="text-sm font-medium text-foreground">Note History</h3>
|
|
134
|
+
<Button
|
|
135
|
+
onClick={() => setShowNoteDialog(true)}
|
|
136
|
+
variant="ghost"
|
|
137
|
+
size="sm"
|
|
138
|
+
className="gap-2 text-primary hover:text-primary"
|
|
139
|
+
>
|
|
140
|
+
<Plus className="h-4 w-4" />
|
|
141
|
+
Add Note
|
|
142
|
+
</Button>
|
|
143
|
+
</div>
|
|
144
|
+
{notes.length === 0 ? (
|
|
145
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
146
|
+
<MessageSquare className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
147
|
+
<p>No notes yet</p>
|
|
148
|
+
</div>
|
|
149
|
+
) : (
|
|
150
|
+
<div className="space-y-3">
|
|
151
|
+
{notes.map((note) => (
|
|
152
|
+
<div
|
|
153
|
+
key={note.id}
|
|
154
|
+
className="border rounded-lg p-4 bg-muted/30"
|
|
155
|
+
>
|
|
156
|
+
<div className="flex items-start justify-between gap-4 mb-2">
|
|
157
|
+
<div className="flex items-center gap-2">
|
|
158
|
+
<p className="font-medium text-sm text-foreground">{note.user}</p>
|
|
159
|
+
<Badge variant={note.type === "RFI Note" ? "default" : "outline"}>
|
|
160
|
+
{note.type}
|
|
161
|
+
</Badge>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="flex items-center gap-2">
|
|
164
|
+
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
|
165
|
+
{note.timestamp}
|
|
166
|
+
</span>
|
|
167
|
+
<Button
|
|
168
|
+
variant="ghost"
|
|
169
|
+
size="sm"
|
|
170
|
+
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
|
|
171
|
+
onClick={() => handleDeleteNote(note.id, note.content)}
|
|
172
|
+
>
|
|
173
|
+
<Trash2 className="h-4 w-4" />
|
|
174
|
+
</Button>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<p className="text-sm text-foreground">{note.content}</p>
|
|
178
|
+
</div>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|