mirror of
https://github.com/borbann-platform/backend-api.git
synced 2025-12-18 20:24:05 +01:00
Merge pull request #2 from Sosokker/develop
ui: add pipeline UI scaffold
This commit is contained in:
commit
2073befd68
@ -1,407 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
|
||||||
import { ArrowLeft, Globe, FileUp, DatabaseIcon, Plus, Trash2, BrainCircuit } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
|
||||||
import PageHeader from "@/components/page-header"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
|
||||||
import { Switch } from "@/components/ui/switch"
|
|
||||||
|
|
||||||
export default function CreatePipelinePage() {
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto p-6">
|
|
||||||
<PageHeader
|
|
||||||
title="Create Data Pipeline"
|
|
||||||
description="Set up a new automated data collection pipeline"
|
|
||||||
breadcrumb={[
|
|
||||||
{ title: "Home", href: "/" },
|
|
||||||
{ title: "Data Pipeline", href: "/data-pipeline" },
|
|
||||||
{ title: "Create", href: "/data-pipeline/create" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mt-6">
|
|
||||||
<Link href="/data-pipeline">
|
|
||||||
<Button variant="outline" className="mb-6">
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Pipelines
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Pipeline Details</CardTitle>
|
|
||||||
<CardDescription>Basic information about your data pipeline</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="name">Pipeline Name</Label>
|
|
||||||
<Input id="name" placeholder="e.g., Property Listings Pipeline" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="description">Description</Label>
|
|
||||||
<Textarea
|
|
||||||
id="description"
|
|
||||||
placeholder="Describe what this pipeline collects and how it will be used"
|
|
||||||
rows={4}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="tags">Tags (optional)</Label>
|
|
||||||
<Input id="tags" placeholder="e.g., real-estate, properties, listings" />
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">Separate tags with commas</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="mt-6 border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-lg flex items-center">
|
|
||||||
<BrainCircuit className="mr-2 h-5 w-5 text-primary" />
|
|
||||||
AI Assistant
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>Customize how AI processes your data</CardDescription>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="ai-prompt">Additional Instructions for AI</Label>
|
|
||||||
<Textarea
|
|
||||||
id="ai-prompt"
|
|
||||||
placeholder="E.g., Focus on extracting pricing trends, ignore promotional content, prioritize property features..."
|
|
||||||
rows={4}
|
|
||||||
className="border-primary/20"
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Provide specific instructions to guide the AI in processing your data sources
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3 pt-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label htmlFor="detect-fields">Auto-detect common fields</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">Automatically identify price, location, etc.</p>
|
|
||||||
</div>
|
|
||||||
<Switch id="detect-fields" defaultChecked />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label htmlFor="suggest-mappings">Suggest field mappings</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Get AI suggestions for matching fields across sources
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch id="suggest-mappings" defaultChecked />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label htmlFor="deduplicate">Deduplicate records</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">Remove duplicate entries automatically</p>
|
|
||||||
</div>
|
|
||||||
<Switch id="deduplicate" defaultChecked />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Data Sources</CardTitle>
|
|
||||||
<CardDescription>Add one or more data sources to your pipeline</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Accordion type="single" collapsible className="w-full" defaultValue="source-1">
|
|
||||||
<AccordionItem value="source-1" className="border rounded-md mb-4 data-source-card active">
|
|
||||||
<AccordionTrigger className="px-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Globe className="mr-2 h-5 w-5 text-primary" />
|
|
||||||
<span>Website Source #1</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="px-4 pb-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="url-1">Website URL</Label>
|
|
||||||
<Input id="url-1" placeholder="https://example.com/listings" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="additional-urls-1">Additional URLs (optional)</Label>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
Pattern Detection
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<Textarea
|
|
||||||
id="additional-urls-1"
|
|
||||||
placeholder="https://example.com/listings/page2 https://example.com/listings/page3"
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Add multiple URLs from the same website (one per line)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button variant="outline" size="sm" className="text-destructive">
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
Remove Source
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem value="source-2" className="border rounded-md mb-4 data-source-card">
|
|
||||||
<AccordionTrigger className="px-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<FileUp className="mr-2 h-5 w-5 text-primary" />
|
|
||||||
<span>File Upload Source #1</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="px-4 pb-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="file-upload-1">Upload File</Label>
|
|
||||||
<div className="flex items-center justify-center p-6 border-2 border-dashed rounded-lg">
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Drag and drop your file here, or click to browse
|
|
||||||
</p>
|
|
||||||
<Button variant="outline" className="mt-2">
|
|
||||||
Select File
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
|
||||||
Supported formats: CSV, JSON, Excel, XML (up to 50MB)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button variant="outline" size="sm" className="text-destructive">
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
Remove Source
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem value="source-3" className="border rounded-md mb-4 data-source-card">
|
|
||||||
<AccordionTrigger className="px-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<DatabaseIcon className="mr-2 h-5 w-5 text-primary" />
|
|
||||||
<span>API Source #1</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="px-4 pb-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="api-url-1">API Endpoint URL</Label>
|
|
||||||
<Input id="api-url-1" placeholder="https://api.example.com/data" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="auth-type-1">Authentication Type</Label>
|
|
||||||
<select
|
|
||||||
id="auth-type-1"
|
|
||||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
<option value="none">None</option>
|
|
||||||
<option value="basic">Basic Auth</option>
|
|
||||||
<option value="bearer">Bearer Token</option>
|
|
||||||
<option value="api-key">API Key</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button variant="outline" size="sm" className="text-destructive">
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
Remove Source
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 mt-4">
|
|
||||||
<Button variant="outline" className="w-full justify-start gap-2">
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Add Website Source
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="w-full justify-start gap-2">
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Add File Upload Source
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="w-full justify-start gap-2">
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Add API Source
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="mt-6 border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Schedule & Automation</CardTitle>
|
|
||||||
<CardDescription>Configure when and how your pipeline should run</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="frequency">Run Frequency</Label>
|
|
||||||
<select
|
|
||||||
id="frequency"
|
|
||||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
<option value="manual">Manual (Run on demand)</option>
|
|
||||||
<option value="hourly">Hourly</option>
|
|
||||||
<option value="daily">Daily</option>
|
|
||||||
<option value="weekly">Weekly</option>
|
|
||||||
<option value="monthly">Monthly</option>
|
|
||||||
<option value="custom">Custom Schedule</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="timezone">Timezone</Label>
|
|
||||||
<select
|
|
||||||
id="timezone"
|
|
||||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
<option value="utc">UTC</option>
|
|
||||||
<option value="est">Eastern Time (ET)</option>
|
|
||||||
<option value="cst">Central Time (CT)</option>
|
|
||||||
<option value="mst">Mountain Time (MT)</option>
|
|
||||||
<option value="pst">Pacific Time (PT)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="max-records">Collection Limits</Label>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="limit-records"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
/>
|
|
||||||
<div className="flex-1">
|
|
||||||
<Label htmlFor="limit-records" className="text-sm font-normal">
|
|
||||||
Limit total records
|
|
||||||
</Label>
|
|
||||||
<Input id="max-records" type="number" placeholder="e.g., 1000" className="mt-1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="stop-no-new"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
/>
|
|
||||||
<Label htmlFor="stop-no-new" className="text-sm font-normal">
|
|
||||||
Stop when no new records found
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="notifications">Notifications</Label>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="notify-complete"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
defaultChecked
|
|
||||||
/>
|
|
||||||
<Label htmlFor="notify-complete" className="text-sm font-normal">
|
|
||||||
Notify when pipeline completes
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="notify-error"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
defaultChecked
|
|
||||||
/>
|
|
||||||
<Label htmlFor="notify-error" className="text-sm font-normal">
|
|
||||||
Notify on errors
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Input id="email" type="email" placeholder="Email for notifications" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="retry-settings">Retry Settings</Label>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="retry-attempts" className="text-sm font-normal">
|
|
||||||
Retry Attempts
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="retry-attempts"
|
|
||||||
type="number"
|
|
||||||
placeholder="e.g., 3"
|
|
||||||
defaultValue="3"
|
|
||||||
className="w-24"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="retry-delay" className="text-sm font-normal">
|
|
||||||
Delay Between Retries (minutes)
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="retry-delay"
|
|
||||||
type="number"
|
|
||||||
placeholder="e.g., 5"
|
|
||||||
defaultValue="5"
|
|
||||||
className="w-24"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end space-x-4">
|
|
||||||
<Button variant="outline">Save as Draft</Button>
|
|
||||||
<Button>Create Pipeline</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,242 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Clock, Database, Play, Plus, RefreshCw, Pause, AlertTriangle, Copy } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
|
||||||
import PageHeader from "@/components/page-header"
|
|
||||||
|
|
||||||
export default function DataPipelinePage() {
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto p-6">
|
|
||||||
<PageHeader
|
|
||||||
title="Data Pipelines"
|
|
||||||
description="Manage your automated data collection pipelines"
|
|
||||||
breadcrumb={[
|
|
||||||
{ title: "Home", href: "/" },
|
|
||||||
{ title: "Data Pipeline", href: "/data-pipeline" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-6">
|
|
||||||
<Tabs defaultValue="active" className="w-full">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="active">Active Pipelines</TabsTrigger>
|
|
||||||
<TabsTrigger value="paused">Paused</TabsTrigger>
|
|
||||||
<TabsTrigger value="all">All Pipelines</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<div className="flex justify-end mt-4">
|
|
||||||
<Link href="/data-pipeline/create">
|
|
||||||
<Button className="gap-2">
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Create Pipeline
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TabsContent value="active" className="mt-4">
|
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<PipelineCard
|
|
||||||
title="Property Listings"
|
|
||||||
description="Scrapes real estate listings from multiple websites"
|
|
||||||
status="active"
|
|
||||||
lastRun="2 hours ago"
|
|
||||||
nextRun="Tomorrow at 9:00 AM"
|
|
||||||
sources={3}
|
|
||||||
records={1240}
|
|
||||||
aiPowered={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PipelineCard
|
|
||||||
title="Rental Market Data"
|
|
||||||
description="Collects rental prices and availability"
|
|
||||||
status="active"
|
|
||||||
lastRun="Yesterday"
|
|
||||||
nextRun="In 3 days"
|
|
||||||
sources={2}
|
|
||||||
records={830}
|
|
||||||
aiPowered={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PipelineCard
|
|
||||||
title="Price Comparison"
|
|
||||||
description="Tracks property price changes over time"
|
|
||||||
status="error"
|
|
||||||
lastRun="2 days ago"
|
|
||||||
nextRun="Scheduled retry in 12 hours"
|
|
||||||
sources={4}
|
|
||||||
records={1560}
|
|
||||||
error="Connection timeout on 1 source"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="paused" className="mt-4">
|
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<PipelineCard
|
|
||||||
title="Commercial Properties"
|
|
||||||
description="Collects data on commercial real estate"
|
|
||||||
status="paused"
|
|
||||||
lastRun="1 week ago"
|
|
||||||
nextRun="Paused"
|
|
||||||
sources={2}
|
|
||||||
records={450}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="all" className="mt-4">
|
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
<PipelineCard
|
|
||||||
title="Property Listings"
|
|
||||||
description="Scrapes real estate listings from multiple websites"
|
|
||||||
status="active"
|
|
||||||
lastRun="2 hours ago"
|
|
||||||
nextRun="Tomorrow at 9:00 AM"
|
|
||||||
sources={3}
|
|
||||||
records={1240}
|
|
||||||
aiPowered={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PipelineCard
|
|
||||||
title="Rental Market Data"
|
|
||||||
description="Collects rental prices and availability"
|
|
||||||
status="active"
|
|
||||||
lastRun="Yesterday"
|
|
||||||
nextRun="In 3 days"
|
|
||||||
sources={2}
|
|
||||||
records={830}
|
|
||||||
aiPowered={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PipelineCard
|
|
||||||
title="Price Comparison"
|
|
||||||
description="Tracks property price changes over time"
|
|
||||||
status="error"
|
|
||||||
lastRun="2 days ago"
|
|
||||||
nextRun="Scheduled retry in 12 hours"
|
|
||||||
sources={4}
|
|
||||||
records={1560}
|
|
||||||
error="Connection timeout on 1 source"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PipelineCard
|
|
||||||
title="Commercial Properties"
|
|
||||||
description="Collects data on commercial real estate"
|
|
||||||
status="paused"
|
|
||||||
lastRun="1 week ago"
|
|
||||||
nextRun="Paused"
|
|
||||||
sources={2}
|
|
||||||
records={450}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PipelineCardProps {
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
status: "active" | "paused" | "error"
|
|
||||||
lastRun: string
|
|
||||||
nextRun: string
|
|
||||||
sources: number
|
|
||||||
records: number
|
|
||||||
error?: string
|
|
||||||
aiPowered?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function PipelineCard({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
status,
|
|
||||||
lastRun,
|
|
||||||
nextRun,
|
|
||||||
sources,
|
|
||||||
records,
|
|
||||||
error,
|
|
||||||
aiPowered,
|
|
||||||
}: PipelineCardProps) {
|
|
||||||
return (
|
|
||||||
<Card className={`pipeline-card ${status === "active" ? "border-2 border-green-500 dark:border-green-600" : ""}`}>
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<CardTitle className="text-lg">{title}</CardTitle>
|
|
||||||
<StatusBadge status={status} />
|
|
||||||
</div>
|
|
||||||
<CardDescription>{description}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center text-sm">
|
|
||||||
<Clock className="h-4 w-4 mr-2 text-muted-foreground" />
|
|
||||||
<span className="text-muted-foreground">Last run:</span>
|
|
||||||
<span className="ml-1 font-medium">{lastRun}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-sm">
|
|
||||||
<RefreshCw className="h-4 w-4 mr-2 text-muted-foreground" />
|
|
||||||
<span className="text-muted-foreground">Next run:</span>
|
|
||||||
<span className="ml-1 font-medium">{nextRun}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-sm">
|
|
||||||
<Database className="h-4 w-4 mr-2 text-muted-foreground" />
|
|
||||||
<span className="text-muted-foreground">Sources:</span>
|
|
||||||
<span className="ml-1 font-medium">{sources}</span>
|
|
||||||
<span className="mx-2">•</span>
|
|
||||||
<span className="text-muted-foreground">Records:</span>
|
|
||||||
<span className="ml-1 font-medium">{records}</span>
|
|
||||||
</div>
|
|
||||||
{error && (
|
|
||||||
<div className="flex items-center text-sm text-destructive mt-2">
|
|
||||||
<AlertTriangle className="h-4 w-4 mr-2" />
|
|
||||||
{error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex justify-between">
|
|
||||||
<Link href={`/data-pipeline/${title.toLowerCase().replace(/\s+/g, "-")}`}>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
View Details
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 text-primary border-primary/20 hover:border-primary">
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
{status === "active" ? (
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
|
|
||||||
<Pause className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
|
|
||||||
<Play className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
|
|
||||||
<RefreshCw className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function StatusBadge({ status }: { status: "active" | "paused" | "error" }) {
|
|
||||||
if (status === "active") {
|
|
||||||
return (
|
|
||||||
<Badge variant="default" className="bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700">
|
|
||||||
Active
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
} else if (status === "paused") {
|
|
||||||
return <Badge variant="secondary">Paused</Badge>
|
|
||||||
} else {
|
|
||||||
return <Badge variant="destructive">Error</Badge>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,687 +0,0 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { ArrowLeft, Download, Edit, Play, Trash, Copy, Check, Plus } from "lucide-react"
|
|
||||||
import Link from "next/link"
|
|
||||||
import PageHeader from "@/components/page-header"
|
|
||||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
|
|
||||||
export default function PipelineDetailsPage() {
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto p-6">
|
|
||||||
<PageHeader
|
|
||||||
title="Property Listings Pipeline"
|
|
||||||
breadcrumb={[
|
|
||||||
{ title: "Home", href: "/" },
|
|
||||||
{ title: "Data Pipeline", href: "/data-pipeline" },
|
|
||||||
{ title: "Property Listings", href: "/data-pipeline/property-listings" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-6">
|
|
||||||
<Link href="/data-pipeline">
|
|
||||||
<Button variant="outline">
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
Back to Pipelines
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<Button variant="outline" className="gap-2 border-primary/20 hover:border-primary">
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
Clone
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="gap-2 border-primary/20 hover:border-primary">
|
|
||||||
<Edit className="h-4 w-4" />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="gap-2 border-primary/20 hover:border-primary">
|
|
||||||
<Play className="h-4 w-4" />
|
|
||||||
Run Now
|
|
||||||
</Button>
|
|
||||||
<Button variant="destructive" size="icon">
|
|
||||||
<Trash className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-6 md:grid-cols-3 mt-6">
|
|
||||||
<Card className="border-2 border-green-500 dark:border-green-600">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">Pipeline Status</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-muted-foreground">Status:</span>
|
|
||||||
<Badge
|
|
||||||
variant="default"
|
|
||||||
className="bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700"
|
|
||||||
>
|
|
||||||
Active
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-muted-foreground">Last Run:</span>
|
|
||||||
<span>2 hours ago</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-muted-foreground">Next Run:</span>
|
|
||||||
<span>Tomorrow at 9:00 AM</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-muted-foreground">Run Frequency:</span>
|
|
||||||
<span>Daily</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-muted-foreground">Total Records:</span>
|
|
||||||
<span>1,240</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">Data Sources</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="p-3 border-2 rounded-md hover:border-highlight-border transition-all duration-200">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-medium">example-realty.com</span>
|
|
||||||
<Badge>Website</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">Last updated: 2 hours ago</p>
|
|
||||||
<p className="text-sm mt-1">540 records</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-3 border-2 rounded-md hover:border-highlight-border transition-all duration-200">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-medium">property-listings.com</span>
|
|
||||||
<Badge>Website</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">Last updated: 2 hours ago</p>
|
|
||||||
<p className="text-sm mt-1">420 records</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-3 border-2 rounded-md hover:border-highlight-border transition-all duration-200">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="font-medium">real-estate-api.com</span>
|
|
||||||
<Badge>API</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm text-muted-foreground mt-1">Last updated: 2 hours ago</p>
|
|
||||||
<p className="text-sm mt-1">280 records</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">Export Options</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Accordion type="single" collapsible className="w-full" defaultValue="format-1">
|
|
||||||
<AccordionItem value="format-1" className="border-0">
|
|
||||||
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<AccordionTrigger className="py-1 px-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Download className="mr-2 h-4 w-4 text-primary" />
|
|
||||||
<span>Export as JSON</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pt-2 pb-1 px-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="pretty-json" className="h-4 w-4" defaultChecked />
|
|
||||||
<label htmlFor="pretty-json" className="text-sm">
|
|
||||||
Pretty print
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<Button size="sm" className="w-full">
|
|
||||||
Download JSON
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem value="format-2" className="border-0">
|
|
||||||
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<AccordionTrigger className="py-1 px-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Download className="mr-2 h-4 w-4 text-primary" />
|
|
||||||
<span>Export as CSV</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pt-2 pb-1 px-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="include-headers" className="h-4 w-4" defaultChecked />
|
|
||||||
<label htmlFor="include-headers" className="text-sm">
|
|
||||||
Include headers
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<Button size="sm" className="w-full">
|
|
||||||
Download CSV
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem value="format-3" className="border-0">
|
|
||||||
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<AccordionTrigger className="py-1 px-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Download className="mr-2 h-4 w-4 text-primary" />
|
|
||||||
<span>Export as SQLite</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pt-2 pb-1 px-2">
|
|
||||||
<Button size="sm" className="w-full">
|
|
||||||
Download SQLite
|
|
||||||
</Button>
|
|
||||||
</AccordionContent>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
|
|
||||||
<AccordionItem value="format-4" className="border-0">
|
|
||||||
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<AccordionTrigger className="py-1 px-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Download className="mr-2 h-4 w-4 text-primary" />
|
|
||||||
<span>Export as YAML</span>
|
|
||||||
</div>
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent className="pt-2 pb-1 px-2">
|
|
||||||
<Button size="sm" className="w-full">
|
|
||||||
Download YAML
|
|
||||||
</Button>
|
|
||||||
</AccordionContent>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6">
|
|
||||||
<Tabs defaultValue="schema">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="schema">Data Schema</TabsTrigger>
|
|
||||||
<TabsTrigger value="preview">Data Preview</TabsTrigger>
|
|
||||||
<TabsTrigger value="output">Output Configuration</TabsTrigger>
|
|
||||||
<TabsTrigger value="history">Run History</TabsTrigger>
|
|
||||||
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="schema" className="mt-4">
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<div>
|
|
||||||
<CardTitle>Data Schema & Field Management</CardTitle>
|
|
||||||
<CardDescription>Customize fields detected from your data sources</CardDescription>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h3 className="text-sm font-medium">Detected Fields</h3>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
Refresh Detection
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border rounded-md p-4">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="field-mapping-item flex items-center">
|
|
||||||
<input type="checkbox" id="field-title" className="h-4 w-4 mr-3" defaultChecked />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="field-title" className="font-medium">
|
|
||||||
Title
|
|
||||||
</Label>
|
|
||||||
<Badge className="ml-2 bg-green-500 text-white">Auto-detected</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">Property title or name</p>
|
|
||||||
</div>
|
|
||||||
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
|
||||||
<option>String</option>
|
|
||||||
<option>Number</option>
|
|
||||||
<option>Boolean</option>
|
|
||||||
<option>Date</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field-mapping-item flex items-center">
|
|
||||||
<input type="checkbox" id="field-price" className="h-4 w-4 mr-3" defaultChecked />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="field-price" className="font-medium">
|
|
||||||
Price
|
|
||||||
</Label>
|
|
||||||
<Badge className="ml-2 bg-green-500 text-white">Auto-detected</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">Property price</p>
|
|
||||||
</div>
|
|
||||||
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
|
||||||
<option>Number</option>
|
|
||||||
<option>String</option>
|
|
||||||
<option>Boolean</option>
|
|
||||||
<option>Date</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field-mapping-item flex items-center">
|
|
||||||
<input type="checkbox" id="field-location" className="h-4 w-4 mr-3" defaultChecked />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="field-location" className="font-medium">
|
|
||||||
Location
|
|
||||||
</Label>
|
|
||||||
<Badge className="ml-2 bg-green-500 text-white">Auto-detected</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">Property location</p>
|
|
||||||
</div>
|
|
||||||
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
|
||||||
<option>String</option>
|
|
||||||
<option>Number</option>
|
|
||||||
<option>Boolean</option>
|
|
||||||
<option>Date</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field-mapping-item flex items-center">
|
|
||||||
<input type="checkbox" id="field-bedrooms" className="h-4 w-4 mr-3" defaultChecked />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="field-bedrooms" className="font-medium">
|
|
||||||
Bedrooms
|
|
||||||
</Label>
|
|
||||||
<Badge className="ml-2 bg-green-500 text-white">Auto-detected</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">Number of bedrooms</p>
|
|
||||||
</div>
|
|
||||||
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
|
||||||
<option>Number</option>
|
|
||||||
<option>String</option>
|
|
||||||
<option>Boolean</option>
|
|
||||||
<option>Date</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field-mapping-item flex items-center">
|
|
||||||
<input type="checkbox" id="field-bathrooms" className="h-4 w-4 mr-3" defaultChecked />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="field-bathrooms" className="font-medium">
|
|
||||||
Bathrooms
|
|
||||||
</Label>
|
|
||||||
<Badge className="ml-2 bg-green-500 text-white">Auto-detected</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">Number of bathrooms</p>
|
|
||||||
</div>
|
|
||||||
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
|
||||||
<option>Number</option>
|
|
||||||
<option>String</option>
|
|
||||||
<option>Boolean</option>
|
|
||||||
<option>Date</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="field-mapping-item flex items-center border-dashed">
|
|
||||||
<input type="checkbox" id="field-custom" className="h-4 w-4 mr-3" />
|
|
||||||
<div className="flex-1">
|
|
||||||
<Input placeholder="Add custom field" className="border-none text-sm p-0 h-6" />
|
|
||||||
</div>
|
|
||||||
<Button variant="ghost" size="sm" className="h-8 px-2">
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2 mt-4">
|
|
||||||
<Label htmlFor="derived-fields">Derived Fields</Label>
|
|
||||||
<Card className="border border-dashed">
|
|
||||||
<CardHeader className="py-3">
|
|
||||||
<CardTitle className="text-sm">Create calculated fields</CardTitle>
|
|
||||||
<CardDescription>Use formulas to generate new fields from existing data</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="py-0">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="field-mapping-item">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label className="font-medium">Price Per Square Foot</Label>
|
|
||||||
<Badge variant="outline">Derived</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center mt-2">
|
|
||||||
<span className="text-xs text-muted-foreground mr-2">Formula:</span>
|
|
||||||
<code className="text-xs bg-muted/50 p-1 rounded">price / squareFeet</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button variant="outline" size="sm" className="w-full">
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
Add Derived Field
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button variant="outline" className="gap-2 mr-2">
|
|
||||||
Reset to Default
|
|
||||||
</Button>
|
|
||||||
<Button>Save Field Configuration</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="preview" className="mt-4">
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Data Preview</CardTitle>
|
|
||||||
<CardDescription>Sample of the collected data</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="border rounded-md overflow-x-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead className="bg-muted">
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">ID</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Title</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Price</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Bedrooms</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Bathrooms</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Location</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Sq. Ft.</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y">
|
|
||||||
<tr>
|
|
||||||
<td className="px-4 py-2 text-sm">P001</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Modern Apartment</td>
|
|
||||||
<td className="px-4 py-2 text-sm">$350,000</td>
|
|
||||||
<td className="px-4 py-2 text-sm">2</td>
|
|
||||||
<td className="px-4 py-2 text-sm">2</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Downtown</td>
|
|
||||||
<td className="px-4 py-2 text-sm">1,200</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td className="px-4 py-2 text-sm">P002</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Luxury Villa</td>
|
|
||||||
<td className="px-4 py-2 text-sm">$1,250,000</td>
|
|
||||||
<td className="px-4 py-2 text-sm">5</td>
|
|
||||||
<td className="px-4 py-2 text-sm">4</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Suburbs</td>
|
|
||||||
<td className="px-4 py-2 text-sm">3,500</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td className="px-4 py-2 text-sm">P003</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Cozy Studio</td>
|
|
||||||
<td className="px-4 py-2 text-sm">$180,000</td>
|
|
||||||
<td className="px-4 py-2 text-sm">1</td>
|
|
||||||
<td className="px-4 py-2 text-sm">1</td>
|
|
||||||
<td className="px-4 py-2 text-sm">City Center</td>
|
|
||||||
<td className="px-4 py-2 text-sm">650</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="output" className="mt-4">
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Output Configuration</CardTitle>
|
|
||||||
<CardDescription>Configure how your data will be structured and exported</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Output Format</Label>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div className="border rounded-md p-3 data-source-card active">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="font-medium">JSON</span>
|
|
||||||
<Check className="h-4 w-4 text-primary" />
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">Structured data format</p>
|
|
||||||
</div>
|
|
||||||
<div className="border rounded-md p-3 data-source-card">
|
|
||||||
<span className="font-medium">CSV</span>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">Spreadsheet compatible</p>
|
|
||||||
</div>
|
|
||||||
<div className="border rounded-md p-3 data-source-card">
|
|
||||||
<span className="font-medium">SQLite</span>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">Portable database</p>
|
|
||||||
</div>
|
|
||||||
<div className="border rounded-md p-3 data-source-card">
|
|
||||||
<span className="font-medium">YAML</span>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">Human-readable format</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label>Format Preview</Label>
|
|
||||||
<Badge variant="outline">Sample Data</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="bg-muted/50 p-3 rounded-md overflow-x-auto">
|
|
||||||
<pre className="text-xs">
|
|
||||||
{`{
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"id": "P001",
|
|
||||||
"title": "Modern Apartment",
|
|
||||||
"price": 350000,
|
|
||||||
"bedrooms": 2,
|
|
||||||
"location": "Downtown"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "P002",
|
|
||||||
"title": "Luxury Villa",
|
|
||||||
"price": 1250000,
|
|
||||||
"bedrooms": 5,
|
|
||||||
"location": "Suburbs"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button className="gap-2">
|
|
||||||
<Download className="h-4 w-4" />
|
|
||||||
Export Data
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="history" className="mt-4">
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Run History</CardTitle>
|
|
||||||
<CardDescription>History of pipeline executions</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="border rounded-md overflow-hidden">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead className="bg-muted">
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Run ID</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Date</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Status</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Duration</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Records</th>
|
|
||||||
<th className="px-4 py-2 text-left text-sm font-medium">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y">
|
|
||||||
<tr>
|
|
||||||
<td className="px-4 py-2 text-sm">RUN-123</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Today, 10:30 AM</td>
|
|
||||||
<td className="px-4 py-2 text-sm">
|
|
||||||
<Badge variant="default" className="bg-success hover:bg-success">
|
|
||||||
Success
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2 text-sm">2m 15s</td>
|
|
||||||
<td className="px-4 py-2 text-sm">1,240</td>
|
|
||||||
<td className="px-4 py-2 text-sm">
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
View Log
|
|
||||||
</Button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td className="px-4 py-2 text-sm">RUN-122</td>
|
|
||||||
<td className="px-4 py-2 text-sm">Yesterday, 10:30 AM</td>
|
|
||||||
<td className="px-4 py-2 text-sm">
|
|
||||||
<Badge variant="default" className="bg-success hover:bg-success">
|
|
||||||
Success
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2 text-sm">2m 10s</td>
|
|
||||||
<td className="px-4 py-2 text-sm">1,235</td>
|
|
||||||
<td className="px-4 py-2 text-sm">
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
View Log
|
|
||||||
</Button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td className="px-4 py-2 text-sm">RUN-121</td>
|
|
||||||
<td className="px-4 py-2 text-sm">2 days ago, 10:30 AM</td>
|
|
||||||
<td className="px-4 py-2 text-sm">
|
|
||||||
<Badge variant="default" className="bg-success hover:bg-success">
|
|
||||||
Success
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-2 text-sm">2m 05s</td>
|
|
||||||
<td className="px-4 py-2 text-sm">1,228</td>
|
|
||||||
<td className="px-4 py-2 text-sm">
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
View Log
|
|
||||||
</Button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="settings" className="mt-4">
|
|
||||||
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Pipeline Settings</CardTitle>
|
|
||||||
<CardDescription>Configure pipeline behavior</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-lg font-medium">Scheduling</h3>
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="frequency">Run Frequency</Label>
|
|
||||||
<select
|
|
||||||
id="frequency"
|
|
||||||
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
defaultValue="daily"
|
|
||||||
>
|
|
||||||
<option value="manual">Manual (Run on demand)</option>
|
|
||||||
<option value="hourly">Hourly</option>
|
|
||||||
<option value="daily">Daily</option>
|
|
||||||
<option value="weekly">Weekly</option>
|
|
||||||
<option value="monthly">Monthly</option>
|
|
||||||
<option value="custom">Custom Schedule</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="time">Run Time</Label>
|
|
||||||
<Input id="time" type="time" defaultValue="09:00" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-lg font-medium">Data Collection</h3>
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="max-records">Maximum Records</Label>
|
|
||||||
<Input id="max-records" type="number" defaultValue="2000" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="retry-attempts">Retry Attempts</Label>
|
|
||||||
<Input id="retry-attempts" type="number" defaultValue="3" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="text-lg font-medium">Notifications</h3>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="notify-complete"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
defaultChecked
|
|
||||||
/>
|
|
||||||
<Label htmlFor="notify-complete" className="text-sm font-normal">
|
|
||||||
Notify when pipeline completes
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="notify-error"
|
|
||||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
|
||||||
defaultChecked
|
|
||||||
/>
|
|
||||||
<Label htmlFor="notify-error" className="text-sm font-normal">
|
|
||||||
Notify on errors
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button>Save Settings</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,654 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useState, useRef } from "react"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Slider } from "@/components/ui/slider"
|
|
||||||
import { Switch } from "@/components/ui/switch"
|
|
||||||
import {
|
|
||||||
MapPin,
|
|
||||||
Home,
|
|
||||||
BarChart2,
|
|
||||||
Filter,
|
|
||||||
MessageCircle,
|
|
||||||
X,
|
|
||||||
Plus,
|
|
||||||
Minus,
|
|
||||||
Droplets,
|
|
||||||
Wind,
|
|
||||||
Sun,
|
|
||||||
LineChart,
|
|
||||||
Send,
|
|
||||||
Newspaper,
|
|
||||||
Building,
|
|
||||||
BedDouble,
|
|
||||||
Bath,
|
|
||||||
Star,
|
|
||||||
Clock,
|
|
||||||
RefreshCw,
|
|
||||||
} from "lucide-react"
|
|
||||||
import Link from "next/link"
|
|
||||||
import { TopNavigation } from "@/components/navigation/top-navigation"
|
|
||||||
|
|
||||||
export default function MapsPage() {
|
|
||||||
const [showFilters, setShowFilters] = useState(false)
|
|
||||||
const [showAnalytics, setShowAnalytics] = useState(false)
|
|
||||||
const [showChat, setShowChat] = useState(false)
|
|
||||||
const [showPropertyInfo, setShowPropertyInfo] = useState(false)
|
|
||||||
const [activeTab, setActiveTab] = useState("basic")
|
|
||||||
const [priceRange, setPriceRange] = useState([5000000, 20000000])
|
|
||||||
const [radius, setRadius] = useState(30)
|
|
||||||
const [message, setMessage] = useState("")
|
|
||||||
const [messages, setMessages] = useState([{ role: "assistant", content: "Hi! How can I help you today?" }])
|
|
||||||
const [mapZoom, setMapZoom] = useState(14)
|
|
||||||
const [selectedModel, setSelectedModel] = useState("Standard ML Model v2.4")
|
|
||||||
const mapRef = useRef(null)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleSendMessage = () => {
|
|
||||||
if (message.trim()) {
|
|
||||||
setMessages([...messages, { role: "user", content: message }])
|
|
||||||
// Simulate AI response
|
|
||||||
setTimeout(() => {
|
|
||||||
setMessages((prev) => [
|
|
||||||
...prev,
|
|
||||||
{
|
|
||||||
role: "assistant",
|
|
||||||
content:
|
|
||||||
"I can provide information about properties in this area. Would you like to know about flood risks, air quality, or nearby amenities?",
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}, 1000)
|
|
||||||
setMessage("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleZoomIn = () => {
|
|
||||||
setMapZoom((prev) => Math.min(prev + 1, 20))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleZoomOut = () => {
|
|
||||||
setMapZoom((prev) => Math.max(prev - 1, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePropertyClick = () => {
|
|
||||||
setShowPropertyInfo(true)
|
|
||||||
setShowFilters(false)
|
|
||||||
setShowChat(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative h-screen w-full overflow-hidden bg-gray-100 dark:bg-gray-900">
|
|
||||||
{/* Map Container */}
|
|
||||||
<div className="absolute inset-0 bg-[url('/map.png')] bg-cover bg-center">
|
|
||||||
{/* Map Placeholder - In a real implementation, this would be a map component */}
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
|
||||||
<div className="text-2xl text-muted-foreground opacity-0">Map View</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sample Property Markers */}
|
|
||||||
<div className="absolute left-1/4 top-1/3 text-primary cursor-pointer group" onClick={handlePropertyClick}>
|
|
||||||
<div className="relative transition-transform transform group-hover:scale-125">
|
|
||||||
<div className="absolute inset-0 w-10 h-10 bg-green-500 opacity-30 blur-lg rounded-full"></div>
|
|
||||||
<MapPin className="h-10 w-10 text-green-500 drop-shadow-xl" />
|
|
||||||
<div className="absolute -top-2 -right-2 h-5 w-5 bg-green-500 rounded-full border-2 border-white animate-pulse"></div>
|
|
||||||
<span className="absolute top-12 left-1/2 -translate-x-1/2 hidden group-hover:flex bg-black text-white text-xs font-bold px-3 py-1 rounded-lg shadow-lg">
|
|
||||||
Available
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute left-1/2 top-1/2 text-primary cursor-pointer group" onClick={handlePropertyClick}>
|
|
||||||
<div className="relative transition-transform transform group-hover:scale-125">
|
|
||||||
<div className="absolute inset-0 w-10 h-10 bg-yellow-500 opacity-30 blur-lg rounded-full"></div>
|
|
||||||
<MapPin className="h-10 w-10 text-yellow-500 drop-shadow-xl" />
|
|
||||||
<div className="absolute -top-2 -right-2 h-5 w-5 bg-amber-500 rounded-full border-2 border-white animate-pulse"></div>
|
|
||||||
<span className="absolute top-12 left-1/2 -translate-x-1/2 hidden group-hover:flex bg-black text-white text-xs font-bold px-3 py-1 rounded-lg shadow-lg">
|
|
||||||
Pending
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute right-1/4 top-2/3 text-primary cursor-pointer group" onClick={handlePropertyClick}>
|
|
||||||
<div className="relative transition-transform transform group-hover:scale-125">
|
|
||||||
<div className="absolute inset-0 w-10 h-10 bg-red-500 opacity-30 blur-lg rounded-full"></div>
|
|
||||||
<MapPin className="h-10 w-10 text-red-500 drop-shadow-xl" />
|
|
||||||
<div className="absolute -top-2 -right-2 h-5 w-5 bg-red-500 rounded-full border-2 border-white animate-pulse"></div>
|
|
||||||
<span className="absolute top-12 left-1/2 -translate-x-1/2 hidden group-hover:flex bg-black text-white text-xs font-bold px-3 py-1 rounded-lg shadow-lg">
|
|
||||||
Sold
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Top Navigation Bar */}
|
|
||||||
<TopNavigation />
|
|
||||||
|
|
||||||
{/* Map Controls */}
|
|
||||||
<div className="absolute top-20 right-4 flex flex-col gap-2 z-10">
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className="h-10 w-10 rounded-full bg-background/95 backdrop-blur-sm shadow-md"
|
|
||||||
onClick={handleZoomIn}
|
|
||||||
>
|
|
||||||
<Plus className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className="h-10 w-10 rounded-full bg-background/95 backdrop-blur-sm shadow-md"
|
|
||||||
onClick={handleZoomOut}
|
|
||||||
>
|
|
||||||
<Minus className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Map Overlay Controls */}
|
|
||||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-2 z-10">
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className={`h-12 w-12 rounded-full bg-background/95 backdrop-blur-sm shadow-md ${showAnalytics ? "bg-primary text-primary-foreground" : ""}`}
|
|
||||||
onClick={() => {
|
|
||||||
setShowAnalytics(!showAnalytics)
|
|
||||||
if (showAnalytics) {
|
|
||||||
setShowFilters(false)
|
|
||||||
setShowPropertyInfo(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BarChart2 className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className={`h-12 w-12 rounded-full bg-background/95 backdrop-blur-sm shadow-md ${showFilters ? "bg-primary text-primary-foreground" : ""}`}
|
|
||||||
onClick={() => {
|
|
||||||
setShowFilters(!showFilters)
|
|
||||||
if (showFilters) {
|
|
||||||
setShowAnalytics(false)
|
|
||||||
setShowPropertyInfo(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Filter className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className={`h-12 w-12 rounded-full bg-background/95 backdrop-blur-sm shadow-md ${showChat ? "bg-primary text-primary-foreground" : ""}`}
|
|
||||||
onClick={() => {
|
|
||||||
setShowChat(!showChat)
|
|
||||||
if (showChat) {
|
|
||||||
setShowPropertyInfo(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MessageCircle className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Property Info Panel */}
|
|
||||||
{showPropertyInfo && (
|
|
||||||
<div className="absolute top-20 right-4 w-96 map-overlay z-20">
|
|
||||||
<div className="map-overlay-header">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Building className="h-5 w-5 text-primary" />
|
|
||||||
<span className="font-medium">Property Details</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setShowPropertyInfo(false)}>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="map-overlay-content">
|
|
||||||
<div className="relative mb-4">
|
|
||||||
<img
|
|
||||||
src="/placeholder.svg?height=200&width=400"
|
|
||||||
alt="Property"
|
|
||||||
className="w-full h-40 object-cover rounded-md"
|
|
||||||
/>
|
|
||||||
<div className="absolute top-2 left-2 flex gap-1">
|
|
||||||
<Badge className="bg-primary">Condominium</Badge>
|
|
||||||
<Badge className="bg-amber-500">
|
|
||||||
<Star className="h-3 w-3 mr-1" /> Premium
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="font-medium text-lg mb-1">Modern Condominium</h3>
|
|
||||||
<div className="flex items-center text-muted-foreground text-sm mb-2">
|
|
||||||
<MapPin className="h-3.5 w-3.5 mr-1 flex-shrink-0" />
|
|
||||||
<span className="truncate">Sukhumvit, Bangkok</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4 text-sm mb-3">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<BedDouble className="h-4 w-4 mr-1 text-primary" />
|
|
||||||
<span>3 Beds</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Bath className="h-4 w-4 mr-1 text-primary" />
|
|
||||||
<span>2 Baths</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Home className="h-4 w-4 mr-1 text-primary" />
|
|
||||||
<span>150 m²</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="font-semibold text-lg mb-4">฿15,000,000</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-sm mb-2">Environmental Factors</h4>
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
<div className="flex flex-col items-center p-2 border rounded-md">
|
|
||||||
<Droplets className="h-5 w-5 text-blue-500 mb-1" />
|
|
||||||
<span className="text-xs font-medium">Flood Risk</span>
|
|
||||||
<Badge className="mt-1 text-xs bg-amber-500">Moderate</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center p-2 border rounded-md">
|
|
||||||
<Wind className="h-5 w-5 text-purple-500 mb-1" />
|
|
||||||
<span className="text-xs font-medium">Air Quality</span>
|
|
||||||
<Badge className="mt-1 text-xs bg-destructive">Poor</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center p-2 border rounded-md">
|
|
||||||
<Sun className="h-5 w-5 text-amber-500 mb-1" />
|
|
||||||
<span className="text-xs font-medium">Noise</span>
|
|
||||||
<Badge className="mt-1 text-xs bg-green-500">Low</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="font-medium text-sm mb-2">Nearby Facilities</h4>
|
|
||||||
<div className="space-y-1 text-sm">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>BTS Phrom Phong</span>
|
|
||||||
<span className="text-muted-foreground">300m</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>EmQuartier Mall</span>
|
|
||||||
<span className="text-muted-foreground">500m</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span>Benchasiri Park</span>
|
|
||||||
<span className="text-muted-foreground">700m</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Link href="/properties/prop1" className="flex-1">
|
|
||||||
<Button className="w-full">View Details</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/price-prediction" className="flex-1">
|
|
||||||
<Button variant="outline" className="w-full">
|
|
||||||
Price Analysis
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Analytics Panel */}
|
|
||||||
{showAnalytics && (
|
|
||||||
<div className="absolute top-20 right-4 w-96 max-h-[800px] overflow-y-auto z-20 map-overlay">
|
|
||||||
<div className="map-overlay-header">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<BarChart2 className="h-5 w-5 text-primary" />
|
|
||||||
<span className="font-medium">Analytics</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 flex items-center justify-center"
|
|
||||||
onClick={() => setSelectedModel(selectedModel)}
|
|
||||||
>
|
|
||||||
<RefreshCw className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setShowAnalytics(false)}>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="map-overlay-content">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<p className="text-sm text-muted-foreground">Information in radius will be analyzed</p>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
Using: {selectedModel}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<LineChart className="h-4 w-4 text-primary" />
|
|
||||||
<span className="font-medium">Area Price History</span>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl font-bold mb-1">10,000,000 Baht</h3>
|
|
||||||
<p className="text-xs text-muted-foreground mb-3">Overall Price History of this area</p>
|
|
||||||
|
|
||||||
<div className="h-20 w-full relative">
|
|
||||||
{/* Simple line chart simulation */}
|
|
||||||
<div className="absolute bottom-0 left-0 w-full h-px bg-border"></div>
|
|
||||||
<div className="absolute bottom-0 left-0 h-full flex items-end">
|
|
||||||
<div className="w-1/6 h-8 border-b-2 border-r-2 border-primary rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-6 border-b-2 border-r-2 border-primary rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-7 border-b-2 border-r-2 border-primary rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-10 border-b-2 border-r-2 border-primary rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-12 border-b-2 border-r-2 border-primary rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-16 border-b-2 border-r-2 border-primary rounded-br"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="mb-4">
|
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<LineChart className="h-4 w-4 text-primary" />
|
|
||||||
<span className="font-medium">Price Prediction</span>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-2xl font-bold mb-1">15,000,000 Baht</h3>
|
|
||||||
<p className="text-xs text-muted-foreground mb-3">The estimated price based on various factors.</p>
|
|
||||||
|
|
||||||
<div className="h-20 w-full relative">
|
|
||||||
{/* Simple line chart simulation */}
|
|
||||||
<div className="absolute bottom-0 left-0 w-full h-px bg-border"></div>
|
|
||||||
<div className="absolute bottom-0 left-0 h-full flex items-end">
|
|
||||||
<div className="w-1/6 h-4 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-6 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-8 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-10 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-14 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
|
||||||
<div className="w-1/6 h-18 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="p-3">
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<Droplets className="h-5 w-5 text-blue-500 mb-1" />
|
|
||||||
<span className="text-sm font-medium">Flood Factor</span>
|
|
||||||
<Badge className="mt-1 bg-amber-500">Moderate</Badge>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardContent className="p-3">
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<Wind className="h-5 w-5 text-purple-500 mb-1" />
|
|
||||||
<span className="text-sm font-medium">Air Factor</span>
|
|
||||||
<Badge className="mt-1 bg-destructive">Bad</Badge>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Local News Section */}
|
|
||||||
<div className="mb-4">
|
|
||||||
<h4 className="font-medium text-sm mb-2 flex items-center">
|
|
||||||
<Newspaper className="h-4 w-4 mr-1 text-primary" />
|
|
||||||
Local News
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="p-3">
|
|
||||||
<h5 className="text-sm font-medium">New BTS Extension Planned</h5>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
The BTS Skytrain will be extended to cover more areas in Sukhumvit by 2025.
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center text-xs text-muted-foreground mt-1">
|
|
||||||
<Clock className="h-3 w-3 mr-1" />
|
|
||||||
<span>2 days ago</span>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="p-3">
|
|
||||||
<h5 className="text-sm font-medium">Property Tax Changes</h5>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
New property tax regulations will take effect next month affecting luxury condominiums.
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center text-xs text-muted-foreground mt-1">
|
|
||||||
<Clock className="h-3 w-3 mr-1" />
|
|
||||||
<span>1 week ago</span>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button variant="outline" className="w-full gap-2">
|
|
||||||
<MessageCircle className="h-4 w-4" />
|
|
||||||
Chat With AI
|
|
||||||
</Button>
|
|
||||||
<Link href="/price-prediction" className="flex-1">
|
|
||||||
<Button className="w-full">Full Analysis</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Filters Panel */}
|
|
||||||
{showFilters && (
|
|
||||||
<div className="absolute top-20 right-4 w-96 map-overlay z-20">
|
|
||||||
<div className="map-overlay-header">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Filter className="h-5 w-5 text-primary" />
|
|
||||||
<span className="font-medium">Property Filters</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setShowFilters(false)}>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabs defaultValue="basic" value={activeTab} onValueChange={setActiveTab}>
|
|
||||||
<TabsList className="w-full grid grid-cols-2">
|
|
||||||
<TabsTrigger value="basic">Basic</TabsTrigger>
|
|
||||||
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="basic" className="p-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">Area Radius</label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Slider
|
|
||||||
defaultValue={[30]}
|
|
||||||
max={50}
|
|
||||||
min={1}
|
|
||||||
step={1}
|
|
||||||
onValueChange={(value) => setRadius(value[0])}
|
|
||||||
className="flex-1"
|
|
||||||
/>
|
|
||||||
<span className="text-sm w-16 text-right">{radius} km</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">Time Period</label>
|
|
||||||
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
|
||||||
<option value="all">All Time</option>
|
|
||||||
<option value="1m">Last Month</option>
|
|
||||||
<option value="3m">Last 3 Months</option>
|
|
||||||
<option value="6m">Last 6 Months</option>
|
|
||||||
<option value="1y">Last Year</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">Property Type</label>
|
|
||||||
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
|
||||||
<option value="any">Any Type</option>
|
|
||||||
<option value="house">House</option>
|
|
||||||
<option value="condo">Condominium</option>
|
|
||||||
<option value="townhouse">Townhouse</option>
|
|
||||||
<option value="land">Land</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button className="w-full">Apply Filters</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="advanced" className="p-4">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">Price Range</label>
|
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground mb-2">
|
|
||||||
<span>฿{priceRange[0].toLocaleString()}</span>
|
|
||||||
<span>฿{priceRange[1].toLocaleString()}</span>
|
|
||||||
</div>
|
|
||||||
<Slider
|
|
||||||
defaultValue={[5000000, 20000000]}
|
|
||||||
max={50000000}
|
|
||||||
min={1000000}
|
|
||||||
step={1000000}
|
|
||||||
onValueChange={(value) => setPriceRange(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">Environmental Factors</label>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Low Flood Risk</span>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Good Air Quality</span>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm">Low Noise Pollution</span>
|
|
||||||
<Switch />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="text-sm font-medium mb-1.5 block">Facilities Nearby</label>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="bts" className="h-4 w-4" />
|
|
||||||
<label htmlFor="bts" className="text-sm">
|
|
||||||
BTS/MRT Station
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="school" className="h-4 w-4" />
|
|
||||||
<label htmlFor="school" className="text-sm">
|
|
||||||
Schools
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="hospital" className="h-4 w-4" />
|
|
||||||
<label htmlFor="hospital" className="text-sm">
|
|
||||||
Hospitals
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<input type="checkbox" id="mall" className="h-4 w-4" />
|
|
||||||
<label htmlFor="mall" className="text-sm">
|
|
||||||
Shopping Malls
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button className="w-full">Apply Filters</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Chat Panel */}
|
|
||||||
{showChat && (
|
|
||||||
<div className="absolute top-20 right-4 w-96 h-[500px] map-overlay z-20 flex flex-col">
|
|
||||||
<div className="map-overlay-header">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<MessageCircle className="h-5 w-5 text-primary" />
|
|
||||||
<span className="font-medium">Chat Assistant</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setShowChat(false)}>
|
|
||||||
<X className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-3 space-y-3">
|
|
||||||
{messages.map((msg, index) => (
|
|
||||||
<div key={index} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
|
|
||||||
<div
|
|
||||||
className={`max-w-[80%] rounded-lg px-3 py-2 ${
|
|
||||||
msg.role === "user" ? "bg-primary text-primary-foreground" : "bg-muted"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{msg.content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-3 border-t">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Type your message..."
|
|
||||||
className="flex-1 h-10 px-3 rounded-md border border-input bg-background"
|
|
||||||
value={message}
|
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter") handleSendMessage()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button variant="default" size="icon" className="h-10 w-10" onClick={handleSendMessage}>
|
|
||||||
<Send className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Map Legend */}
|
|
||||||
<div className="absolute bottom-8 left-4 bg-background/95 backdrop-blur-sm p-2 rounded-lg shadow-md z-10">
|
|
||||||
<div className="text-xs font-medium mb-1">Property Status</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<div className="h-3 w-3 bg-green-500 rounded-full"></div>
|
|
||||||
<span className="text-xs">Available</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<div className="h-3 w-3 bg-amber-500 rounded-full"></div>
|
|
||||||
<span className="text-xs">Pending</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<div className="h-3 w-3 bg-red-500 rounded-full"></div>
|
|
||||||
<span className="text-xs">Sold</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -0,0 +1,126 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
import { AddDataSource } from "@/components/pipeline/add-data-source";
|
||||||
|
import { PipelineAiAssistant } from "@/components/pipeline/ai-assistant";
|
||||||
|
import { PipelineDetails } from "@/components/pipeline/details";
|
||||||
|
import { ScheduleAndInformation } from "@/components/pipeline/schedule-and-information";
|
||||||
|
import { PipelineSummary } from "@/components/pipeline/summary";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Form } from "@/components/ui/form";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { pipelineSchema, PipelineFormValues } from "@/lib/validations/pipeline";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
|
const TOTAL_STEPS = 5;
|
||||||
|
|
||||||
|
const stepComponents = [
|
||||||
|
<PipelineDetails key="details" />,
|
||||||
|
<AddDataSource key="datasource" />,
|
||||||
|
<PipelineAiAssistant key="ai" />,
|
||||||
|
<ScheduleAndInformation key="schedule" />,
|
||||||
|
<PipelineSummary key="summary" />,
|
||||||
|
,
|
||||||
|
];
|
||||||
|
|
||||||
|
const motionVariants = {
|
||||||
|
enter: { opacity: 0, x: 50 },
|
||||||
|
center: { opacity: 1, x: 0 },
|
||||||
|
exit: { opacity: 0, x: -50 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CratePipelineForm() {
|
||||||
|
const [step, setStep] = useState(0);
|
||||||
|
const isFirstStep = step === 0;
|
||||||
|
const isLastStep = step === TOTAL_STEPS - 1;
|
||||||
|
|
||||||
|
const form = useForm<PipelineFormValues>({
|
||||||
|
resolver: zodResolver(pipelineSchema),
|
||||||
|
});
|
||||||
|
const { handleSubmit, reset } = form;
|
||||||
|
|
||||||
|
const onSubmit = async (formData: unknown) => {
|
||||||
|
if (!isLastStep) return setStep((s) => s + 1);
|
||||||
|
console.log(formData);
|
||||||
|
reset();
|
||||||
|
setStep(0);
|
||||||
|
toast.success("Form successfully submitted");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBack = () => setStep((s) => (s > 0 ? s - 1 : s));
|
||||||
|
|
||||||
|
const StepForm = (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="grid gap-y-4">
|
||||||
|
{stepComponents[step]}
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
className="font-medium cursor-pointer"
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={isFirstStep}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
size="sm"
|
||||||
|
className="font-medium cursor-pointer"
|
||||||
|
>
|
||||||
|
{isLastStep ? "Create Pipeline" : "Next"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* stepper */}
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
{Array.from({ length: TOTAL_STEPS }).map((_, index) => (
|
||||||
|
<div key={index} className="flex items-center">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-4 h-4 rounded-full transition-all duration-300 ease-in-out",
|
||||||
|
index <= step ? "bg-primary" : "bg-primary/30"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{index < TOTAL_STEPS - 1 && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-8 h-0.5",
|
||||||
|
index < step ? "bg-primary" : "bg-primary/30"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* animated form */}
|
||||||
|
<Card className="shadow-sm">
|
||||||
|
<CardContent>
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={step}
|
||||||
|
variants={motionVariants}
|
||||||
|
initial="enter"
|
||||||
|
animate="center"
|
||||||
|
exit="exit"
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
>
|
||||||
|
{StepForm}
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
frontend/app/(sidebar)/data-pipeline/create/page.tsx
Normal file
24
frontend/app/(sidebar)/data-pipeline/create/page.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import CratePipelineForm from "./create-pipeline-multiform";
|
||||||
|
|
||||||
|
export default function CreatePipelinePage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-6">
|
||||||
|
<div className="mt-6">
|
||||||
|
<Link href="/data-pipeline">
|
||||||
|
<Button variant="outline" className="mb-6 cursor-pointer">
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Back to Pipelines
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<CratePipelineForm />
|
||||||
|
<div className="mt-6 flex justify-end space-x-4">
|
||||||
|
{/* <Button variant="outline">Save as Draft</Button>
|
||||||
|
<Button>Create Pipeline</Button> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
159
frontend/app/(sidebar)/data-pipeline/page.tsx
Normal file
159
frontend/app/(sidebar)/data-pipeline/page.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageHeader from "@/components/page-header";
|
||||||
|
import { PipelineCard } from "@/components/pipeline/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Pipeline } from "@/lib/api/pipelines/types";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export default function DataPipelinePage() {
|
||||||
|
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPipelines = async () => {
|
||||||
|
try {
|
||||||
|
// const data = await listPipelines();
|
||||||
|
// setPipelines(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching pipelines:", err);
|
||||||
|
setError("Failed to load pipelines");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPipelines();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-6">
|
||||||
|
{error && <p className="text-red-500">{error}</p>}
|
||||||
|
<PageHeader
|
||||||
|
title="Data Pipelines"
|
||||||
|
description="Manage your automated data collection pipelines"
|
||||||
|
breadcrumb={[
|
||||||
|
{ title: "Home", href: "/" },
|
||||||
|
{ title: "Data Pipeline", href: "/data-pipeline" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mt-6">
|
||||||
|
<Tabs defaultValue="active" className="w-full">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="active">Active Pipelines</TabsTrigger>
|
||||||
|
<TabsTrigger value="paused">Paused</TabsTrigger>
|
||||||
|
<TabsTrigger value="all">All Pipelines</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-4">
|
||||||
|
<Link href="/data-pipeline/create">
|
||||||
|
<Button className="gap-2 cursor-pointer">
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Create Pipeline
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TabsContent value="active" className="mt-4">
|
||||||
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<PipelineCard
|
||||||
|
name="Property Listings"
|
||||||
|
description="Scrapes real estate listings from multiple websites"
|
||||||
|
status="active"
|
||||||
|
lastRun="2 hours ago"
|
||||||
|
nextRun="Tomorrow at 9:00 AM"
|
||||||
|
sources={3}
|
||||||
|
records={1240}
|
||||||
|
aiPowered={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PipelineCard
|
||||||
|
name="Rental Market Data"
|
||||||
|
description="Collects rental prices and availability"
|
||||||
|
status="active"
|
||||||
|
lastRun="Yesterday"
|
||||||
|
nextRun="In 3 days"
|
||||||
|
sources={2}
|
||||||
|
records={830}
|
||||||
|
aiPowered={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PipelineCard
|
||||||
|
name="Price Comparison"
|
||||||
|
description="Tracks property price changes over time"
|
||||||
|
status="error"
|
||||||
|
lastRun="2 days ago"
|
||||||
|
nextRun="Scheduled retry in 12 hours"
|
||||||
|
sources={4}
|
||||||
|
records={1560}
|
||||||
|
error="Connection timeout on 1 source"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="paused" className="mt-4">
|
||||||
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<PipelineCard
|
||||||
|
name="Commercial Properties"
|
||||||
|
description="Collects data on commercial real estate"
|
||||||
|
status="paused"
|
||||||
|
lastRun="1 week ago"
|
||||||
|
nextRun="Paused"
|
||||||
|
sources={2}
|
||||||
|
records={450}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="all" className="mt-4">
|
||||||
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<PipelineCard
|
||||||
|
name="Property Listings"
|
||||||
|
description="Scrapes real estate listings from multiple websites"
|
||||||
|
status="active"
|
||||||
|
lastRun="2 hours ago"
|
||||||
|
nextRun="Tomorrow at 9:00 AM"
|
||||||
|
sources={3}
|
||||||
|
records={1240}
|
||||||
|
aiPowered={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* mock pipeline card with data */}
|
||||||
|
<PipelineCard
|
||||||
|
name="Rental Market Data"
|
||||||
|
description="Collects rental prices and availability"
|
||||||
|
status="active"
|
||||||
|
lastRun="Yesterday"
|
||||||
|
nextRun="In 3 days"
|
||||||
|
sources={2}
|
||||||
|
records={830}
|
||||||
|
aiPowered={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PipelineCard
|
||||||
|
name="Price Comparison"
|
||||||
|
description="Tracks property price changes over time"
|
||||||
|
status="error"
|
||||||
|
lastRun="2 days ago"
|
||||||
|
nextRun="Scheduled retry in 12 hours"
|
||||||
|
sources={4}
|
||||||
|
records={1560}
|
||||||
|
error="Connection timeout on 1 source"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PipelineCard
|
||||||
|
name="Commercial Properties"
|
||||||
|
description="Collects data on commercial real estate"
|
||||||
|
status="paused"
|
||||||
|
lastRun="1 week ago"
|
||||||
|
nextRun="Paused"
|
||||||
|
sources={2}
|
||||||
|
records={450}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
frontend/app/(sidebar)/data-pipeline/property-listings/page.tsx
Normal file
105
frontend/app/(sidebar)/data-pipeline/property-listings/page.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { ArrowLeft, Edit, Play, Trash, Copy } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import PageHeader from "@/components/page-header";
|
||||||
|
import { PipelineStatus } from "@/components/pipeline/status";
|
||||||
|
import { PipelineDataSource } from "@/components/pipeline/data-source";
|
||||||
|
import { PipelineExportData } from "@/components/pipeline/export-data";
|
||||||
|
import { PipelineDataSchema } from "@/components/pipeline/data-schema";
|
||||||
|
import { PipelineDataPreview } from "@/components/pipeline/data-preview";
|
||||||
|
import { PipelineOutputConfig } from "@/components/pipeline/output-config";
|
||||||
|
import { PipelineRunHistory } from "@/components/pipeline/run-history";
|
||||||
|
import { PipelineSettings } from "@/components/pipeline/settings";
|
||||||
|
|
||||||
|
export default function PipelineDetailsPage() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-6">
|
||||||
|
<PageHeader
|
||||||
|
title="Property Listings Pipeline"
|
||||||
|
breadcrumb={[
|
||||||
|
{ title: "Home", href: "/" },
|
||||||
|
{ title: "Data Pipeline", href: "/data-pipeline" },
|
||||||
|
{
|
||||||
|
title: "Property Listings",
|
||||||
|
href: "/data-pipeline/property-listings",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mt-6">
|
||||||
|
<Link href="/data-pipeline">
|
||||||
|
<Button variant="outline">
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Back to Pipelines
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
Clone
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2 border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<Play className="h-4 w-4" />
|
||||||
|
Run Now
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" size="icon">
|
||||||
|
<Trash className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 md:grid-cols-3 mt-6">
|
||||||
|
<PipelineStatus />
|
||||||
|
<PipelineDataSource />
|
||||||
|
<PipelineExportData />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<Tabs defaultValue="schema">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="schema">Data Schema</TabsTrigger>
|
||||||
|
<TabsTrigger value="preview">Data Preview</TabsTrigger>
|
||||||
|
<TabsTrigger value="output">Output Configuration</TabsTrigger>
|
||||||
|
<TabsTrigger value="history">Run History</TabsTrigger>
|
||||||
|
<TabsTrigger value="settings">Settings</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="schema" className="mt-4">
|
||||||
|
<PipelineDataSchema />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="preview" className="mt-4">
|
||||||
|
<PipelineDataPreview />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="output" className="mt-4">
|
||||||
|
<PipelineOutputConfig />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="history" className="mt-4">
|
||||||
|
<PipelineRunHistory />
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="settings" className="mt-4">
|
||||||
|
<PipelineSettings />
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
frontend/app/(sidebar)/layout.tsx
Normal file
16
frontend/app/(sidebar)/layout.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
import Sidebar from "@/components/sidebar";
|
||||||
|
|
||||||
|
export default function AppLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen">
|
||||||
|
<Sidebar />
|
||||||
|
<div className="flex-1 overflow-auto">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
186
frontend/app/(sidebar)/maps/page.tsx
Normal file
186
frontend/app/(sidebar)/maps/page.tsx
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AnalyticsPanel } from "@/components/map/analytics-panel";
|
||||||
|
import { ChatPanel } from "@/components/map/chat-panel";
|
||||||
|
import { FiltersPanel } from "@/components/map/filters-panel";
|
||||||
|
import { PropertyInfoPanel } from "@/components/map/property-info-panel";
|
||||||
|
|
||||||
|
import MapWithSearch from "@/components/map/map-with-search";
|
||||||
|
import { TopNavigation } from "@/components/navigation/top-navigation";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { BarChart2, Filter, MessageCircle } from "lucide-react";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import Draggable from "react-draggable";
|
||||||
|
|
||||||
|
export default function MapsPage() {
|
||||||
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
const [showAnalytics, setShowAnalytics] = useState(false);
|
||||||
|
const [showChat, setShowChat] = useState(false);
|
||||||
|
const [showPropertyInfo, setShowPropertyInfo] = useState(false);
|
||||||
|
const analyticsRef = useRef<HTMLDivElement>(null);
|
||||||
|
const filtersRef = useRef<HTMLDivElement>(null);
|
||||||
|
const chatRef = useRef<HTMLDivElement>(null);
|
||||||
|
const propertyInfoRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handlePropertyClick = () => {
|
||||||
|
setShowPropertyInfo(true);
|
||||||
|
setShowFilters(false);
|
||||||
|
setShowChat(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative h-screen w-full overflow-hidden bg-gray-100 dark:bg-gray-900">
|
||||||
|
<div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<MapWithSearch />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sample Property Markers */}
|
||||||
|
{/* <div
|
||||||
|
className="absolute left-1/4 top-1/3 text-primary cursor-pointer group"
|
||||||
|
onClick={handlePropertyClick}
|
||||||
|
>
|
||||||
|
<div className="relative transition-transform transform group-hover:scale-125">
|
||||||
|
<div className="absolute inset-0 w-10 h-10 bg-green-500 opacity-30 blur-lg rounded-full"></div>
|
||||||
|
<MapPin className="h-10 w-10 text-green-500 drop-shadow-xl" />
|
||||||
|
<div className="absolute -top-2 -right-2 h-5 w-5 bg-green-500 rounded-full border-2 border-white animate-pulse"></div>
|
||||||
|
<span className="absolute top-12 left-1/2 -translate-x-1/2 hidden group-hover:flex bg-black text-white text-xs font-bold px-3 py-1 rounded-lg shadow-lg">
|
||||||
|
Available
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute left-1/2 top-1/2 text-primary cursor-pointer group"
|
||||||
|
onClick={handlePropertyClick}
|
||||||
|
>
|
||||||
|
<div className="relative transition-transform transform group-hover:scale-125">
|
||||||
|
<div className="absolute inset-0 w-10 h-10 bg-yellow-500 opacity-30 blur-lg rounded-full"></div>
|
||||||
|
<MapPin className="h-10 w-10 text-yellow-500 drop-shadow-xl" />
|
||||||
|
<div className="absolute -top-2 -right-2 h-5 w-5 bg-amber-500 rounded-full border-2 border-white animate-pulse"></div>
|
||||||
|
<span className="absolute top-12 left-1/2 -translate-x-1/2 hidden group-hover:flex bg-black text-white text-xs font-bold px-3 py-1 rounded-lg shadow-lg">
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="absolute right-1/4 top-2/3 text-primary cursor-pointer group"
|
||||||
|
onClick={handlePropertyClick}
|
||||||
|
>
|
||||||
|
<div className="relative transition-transform transform group-hover:scale-125">
|
||||||
|
<div className="absolute inset-0 w-10 h-10 bg-red-500 opacity-30 blur-lg rounded-full"></div>
|
||||||
|
<MapPin className="h-10 w-10 text-red-500 drop-shadow-xl" />
|
||||||
|
<div className="absolute -top-2 -right-2 h-5 w-5 bg-red-500 rounded-full border-2 border-white animate-pulse"></div>
|
||||||
|
<span className="absolute top-12 left-1/2 -translate-x-1/2 hidden group-hover:flex bg-black text-white text-xs font-bold px-3 py-1 rounded-lg shadow-lg">
|
||||||
|
Sold
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top Navigation Bar */}
|
||||||
|
<TopNavigation />
|
||||||
|
|
||||||
|
{/* Map Overlay Controls */}
|
||||||
|
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex gap-2 z-10">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
className={`h-12 w-12 rounded-full bg-background/95 backdrop-blur-sm shadow-md ${
|
||||||
|
showAnalytics ? "bg-primary text-primary-foreground" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowAnalytics(!showAnalytics);
|
||||||
|
if (showAnalytics) {
|
||||||
|
setShowPropertyInfo(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BarChart2 className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
className={`h-12 w-12 rounded-full bg-background/95 backdrop-blur-sm shadow-md ${
|
||||||
|
showFilters ? "bg-primary text-primary-foreground" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowFilters(!showFilters);
|
||||||
|
if (showFilters) {
|
||||||
|
setShowPropertyInfo(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Filter className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="icon"
|
||||||
|
className={`h-12 w-12 rounded-full bg-background/95 backdrop-blur-sm shadow-md ${
|
||||||
|
showChat ? "bg-primary text-primary-foreground" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
|
setShowChat(!showChat);
|
||||||
|
if (showChat) {
|
||||||
|
setShowPropertyInfo(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MessageCircle className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Property Info Panel */}
|
||||||
|
{showPropertyInfo && (
|
||||||
|
<Draggable nodeRef={propertyInfoRef as React.RefObject<HTMLElement>}>
|
||||||
|
<div ref={propertyInfoRef}>
|
||||||
|
<PropertyInfoPanel setShowPropertyInfo={setShowPropertyInfo} />
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Analytics Panel */}
|
||||||
|
{showAnalytics && (
|
||||||
|
<Draggable nodeRef={analyticsRef as React.RefObject<HTMLElement>}>
|
||||||
|
<div ref={analyticsRef}>
|
||||||
|
<AnalyticsPanel setShowAnalytics={setShowAnalytics} />
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showFilters && (
|
||||||
|
<Draggable nodeRef={filtersRef as React.RefObject<HTMLElement>}>
|
||||||
|
<div ref={filtersRef}>
|
||||||
|
<FiltersPanel setShowFilters={setShowFilters} />
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showChat && (
|
||||||
|
<Draggable nodeRef={chatRef as React.RefObject<HTMLElement>}>
|
||||||
|
<div ref={chatRef}>
|
||||||
|
<ChatPanel setShowChat={setShowChat} />
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Map Legend */}
|
||||||
|
<div className="absolute bottom-8 left-4 bg-background/95 backdrop-blur-sm p-2 rounded-lg shadow-md z-10">
|
||||||
|
<div className="text-xs font-medium mb-1">Property Status</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className="h-3 w-3 bg-green-500 rounded-full"></div>
|
||||||
|
<span className="text-xs">Available</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className="h-3 w-3 bg-amber-500 rounded-full"></div>
|
||||||
|
<span className="text-xs">Pending</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<div className="h-3 w-3 bg-red-500 rounded-full"></div>
|
||||||
|
<span className="text-xs">Sold</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,31 +1,32 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
// import { ModelCard } from "@/components/models/model-card";
|
||||||
|
import PageHeader from "@/components/page-header";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
Card,
|
||||||
import { Badge } from "@/components/ui/badge";
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { useModelState } from "@/store/model-store";
|
||||||
import {
|
import {
|
||||||
|
AlertTriangle,
|
||||||
BrainCircuit,
|
BrainCircuit,
|
||||||
Clock,
|
Check,
|
||||||
Database,
|
Database,
|
||||||
Play,
|
Play,
|
||||||
Plus,
|
Plus,
|
||||||
Settings,
|
|
||||||
Sliders,
|
|
||||||
Trash2,
|
|
||||||
AlertTriangle,
|
|
||||||
Check,
|
|
||||||
ArrowRight,
|
|
||||||
Info,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import { useState } from "react";
|
||||||
import PageHeader from "@/components/page-header";
|
import { useShallow } from "zustand/react/shallow";
|
||||||
|
|
||||||
export default function ModelsPage() {
|
export default function ModelsPage() {
|
||||||
const [activeTab, setActiveTab] = useState("my-models");
|
const [activeTab, setActiveTab] = useState("my-models");
|
||||||
@ -34,85 +35,36 @@ export default function ModelsPage() {
|
|||||||
const [isTraining, setIsTraining] = useState(false);
|
const [isTraining, setIsTraining] = useState(false);
|
||||||
const [modelName, setModelName] = useState("");
|
const [modelName, setModelName] = useState("");
|
||||||
const [modelDescription, setModelDescription] = useState("");
|
const [modelDescription, setModelDescription] = useState("");
|
||||||
|
const { models } = useModelState(
|
||||||
|
useShallow((state) => ({
|
||||||
|
models: state.models,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const dataPipelines = [
|
const dataPipelines = [
|
||||||
{ id: "pipeline-1", name: "Property Listings", records: 1240, lastUpdated: "2 hours ago" },
|
|
||||||
{ id: "pipeline-2", name: "Rental Market Data", records: 830, lastUpdated: "Yesterday" },
|
|
||||||
{ id: "pipeline-3", name: "Price Comparison", records: 1560, lastUpdated: "2 days ago" },
|
|
||||||
{ id: "pipeline-4", name: "Commercial Properties", records: 450, lastUpdated: "1 week ago" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const models = [
|
|
||||||
{
|
{
|
||||||
id: "model-1",
|
id: "pipeline-1",
|
||||||
name: "Standard ML Model v2.4",
|
name: "Property Listings",
|
||||||
type: "Regression",
|
records: 1240,
|
||||||
hyperparameters: {
|
lastUpdated: "2 hours ago",
|
||||||
learningRate: "0.01",
|
|
||||||
maxDepth: "6",
|
|
||||||
numEstimators: "100",
|
|
||||||
},
|
|
||||||
dataSource: "System Base Model",
|
|
||||||
status: "active",
|
|
||||||
lastUpdated: "3 days ago",
|
|
||||||
isSystem: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "model-2",
|
id: "pipeline-2",
|
||||||
name: "Enhanced Neural Network v1.8",
|
name: "Rental Market Data",
|
||||||
type: "Neural Network",
|
records: 830,
|
||||||
hyperparameters: {
|
lastUpdated: "Yesterday",
|
||||||
layers: "4",
|
},
|
||||||
neurons: "128,64,32,16",
|
{
|
||||||
dropout: "0.2",
|
id: "pipeline-3",
|
||||||
},
|
name: "Price Comparison",
|
||||||
dataSource: "System Base Model",
|
records: 1560,
|
||||||
status: "active",
|
lastUpdated: "2 days ago",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "pipeline-4",
|
||||||
|
name: "Commercial Properties",
|
||||||
|
records: 450,
|
||||||
lastUpdated: "1 week ago",
|
lastUpdated: "1 week ago",
|
||||||
isSystem: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "model-3",
|
|
||||||
name: "Geospatial Regression v3.1",
|
|
||||||
type: "Geospatial",
|
|
||||||
hyperparameters: {
|
|
||||||
spatialWeight: "0.7",
|
|
||||||
kernelType: "gaussian",
|
|
||||||
bandwidth: "adaptive",
|
|
||||||
},
|
|
||||||
dataSource: "System Base Model",
|
|
||||||
status: "active",
|
|
||||||
lastUpdated: "2 weeks ago",
|
|
||||||
isSystem: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "model-4",
|
|
||||||
name: "Time Series Forecast v2.0",
|
|
||||||
type: "Time Series",
|
|
||||||
hyperparameters: {
|
|
||||||
p: "2",
|
|
||||||
d: "1",
|
|
||||||
q: "2",
|
|
||||||
seasonal: "true",
|
|
||||||
},
|
|
||||||
dataSource: "System Base Model",
|
|
||||||
status: "active",
|
|
||||||
lastUpdated: "1 month ago",
|
|
||||||
isSystem: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "model-5",
|
|
||||||
name: "Custom Model (User #1242)",
|
|
||||||
type: "Ensemble",
|
|
||||||
hyperparameters: {
|
|
||||||
baseEstimators: "3",
|
|
||||||
votingMethod: "weighted",
|
|
||||||
weights: "0.4,0.4,0.2",
|
|
||||||
},
|
|
||||||
dataSource: "Property Listings Pipeline",
|
|
||||||
status: "active",
|
|
||||||
lastUpdated: "5 days ago",
|
|
||||||
isSystem: false,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -146,7 +98,11 @@ export default function ModelsPage() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tabs defaultValue="my-models" className="mt-6" onValueChange={setActiveTab}>
|
<Tabs
|
||||||
|
defaultValue="my-models"
|
||||||
|
className="mt-6"
|
||||||
|
onValueChange={setActiveTab}
|
||||||
|
>
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="my-models">My Models</TabsTrigger>
|
<TabsTrigger value="my-models">My Models</TabsTrigger>
|
||||||
@ -155,7 +111,10 @@ export default function ModelsPage() {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{activeTab !== "train-model" && (
|
{activeTab !== "train-model" && (
|
||||||
<Button onClick={() => setActiveTab("train-model")} className="gap-2">
|
<Button
|
||||||
|
onClick={() => setActiveTab("train-model")}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Train New Model
|
Train New Model
|
||||||
</Button>
|
</Button>
|
||||||
@ -164,35 +123,34 @@ export default function ModelsPage() {
|
|||||||
|
|
||||||
<TabsContent value="my-models">
|
<TabsContent value="my-models">
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{models
|
{/* {models} */}
|
||||||
.filter((model) => !model.isSystem)
|
|
||||||
.map((model) => (
|
|
||||||
<ModelCard key={model.id} model={model} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{models.filter((model) => !model.isSystem).length === 0 && (
|
{models && (
|
||||||
<Card className="border-dashed">
|
<Card className="border-dashed">
|
||||||
<CardContent className="pt-6 text-center">
|
<CardContent className="pt-6 text-center">
|
||||||
<BrainCircuit className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
<BrainCircuit className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium mb-2">No Custom Models Yet</h3>
|
<h3 className="text-lg font-medium mb-2">
|
||||||
|
No Custom Models Yet
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Train your first custom model to get started with personalized property predictions.
|
Train your first custom model to get started with personalized
|
||||||
|
property predictions.
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => setActiveTab("train-model")}>Train Your First Model</Button>
|
<Button onClick={() => setActiveTab("train-model")}>
|
||||||
|
Train Your First Model
|
||||||
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="system-models">
|
<TabsContent value="system-models">
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
{/* <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{models
|
{models.map((model) => (
|
||||||
.filter((model) => model.isSystem)
|
<ModelCard model={model} />
|
||||||
.map((model) => (
|
))}
|
||||||
<ModelCard key={model.id} model={model} />
|
</div> */}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="train-model">
|
<TabsContent value="train-model">
|
||||||
@ -215,7 +173,9 @@ export default function ModelsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="model-description">Description (Optional)</Label>
|
<Label htmlFor="model-description">
|
||||||
|
Description (Optional)
|
||||||
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="model-description"
|
id="model-description"
|
||||||
placeholder="Describe the purpose of this model..."
|
placeholder="Describe the purpose of this model..."
|
||||||
@ -237,28 +197,42 @@ export default function ModelsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<h3 className="text-sm font-medium mb-2">Advanced Settings</h3>
|
<h3 className="text-sm font-medium mb-2">
|
||||||
|
Advanced Settings
|
||||||
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="feature-selection">Automatic Feature Selection</Label>
|
<Label htmlFor="feature-selection">
|
||||||
<p className="text-xs text-muted-foreground">Let AI select the most relevant features</p>
|
Automatic Feature Selection
|
||||||
|
</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Let AI select the most relevant features
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch id="feature-selection" defaultChecked />
|
<Switch id="feature-selection" defaultChecked />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="hyperparameter-tuning">Hyperparameter Tuning</Label>
|
<Label htmlFor="hyperparameter-tuning">
|
||||||
<p className="text-xs text-muted-foreground">Optimize model parameters automatically</p>
|
Hyperparameter Tuning
|
||||||
|
</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Optimize model parameters automatically
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch id="hyperparameter-tuning" defaultChecked />
|
<Switch id="hyperparameter-tuning" defaultChecked />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="cross-validation">Cross-Validation</Label>
|
<Label htmlFor="cross-validation">
|
||||||
<p className="text-xs text-muted-foreground">Use k-fold cross-validation</p>
|
Cross-Validation
|
||||||
|
</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Use k-fold cross-validation
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Switch id="cross-validation" defaultChecked />
|
<Switch id="cross-validation" defaultChecked />
|
||||||
</div>
|
</div>
|
||||||
@ -272,7 +246,9 @@ export default function ModelsPage() {
|
|||||||
<Card className="mb-6">
|
<Card className="mb-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Select Data Source</CardTitle>
|
<CardTitle>Select Data Source</CardTitle>
|
||||||
<CardDescription>Choose a data pipeline to train your model</CardDescription>
|
<CardDescription>
|
||||||
|
Choose a data pipeline to train your model
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@ -280,20 +256,26 @@ export default function ModelsPage() {
|
|||||||
<div
|
<div
|
||||||
key={pipeline.id}
|
key={pipeline.id}
|
||||||
className={`p-4 border rounded-lg cursor-pointer transition-all ${
|
className={`p-4 border rounded-lg cursor-pointer transition-all ${
|
||||||
selectedPipeline === pipeline.id ? "border-primary bg-primary/5" : "hover:border-primary/50"
|
selectedPipeline === pipeline.id
|
||||||
|
? "border-primary bg-primary/5"
|
||||||
|
: "hover:border-primary/50"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setSelectedPipeline(pipeline.id)}>
|
onClick={() => setSelectedPipeline(pipeline.id)}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Database className="h-5 w-5 text-primary" />
|
<Database className="h-5 w-5 text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium">{pipeline.name}</h3>
|
<h3 className="font-medium">{pipeline.name}</h3>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{pipeline.records.toLocaleString()} records • Updated {pipeline.lastUpdated}
|
{pipeline.records.toLocaleString()} records •
|
||||||
|
Updated {pipeline.lastUpdated}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{selectedPipeline === pipeline.id && <Check className="h-5 w-5 text-primary" />}
|
{selectedPipeline === pipeline.id && (
|
||||||
|
<Check className="h-5 w-5 text-primary" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -304,7 +286,9 @@ export default function ModelsPage() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Training Process</CardTitle>
|
<CardTitle>Training Process</CardTitle>
|
||||||
<CardDescription>Monitor and control the training process</CardDescription>
|
<CardDescription>
|
||||||
|
Monitor and control the training process
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{isTraining ? (
|
{isTraining ? (
|
||||||
@ -335,7 +319,11 @@ export default function ModelsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{trainingProgress < 100 && (
|
{trainingProgress < 100 && (
|
||||||
<Button variant="outline" className="w-full" onClick={() => setIsTraining(false)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => setIsTraining(false)}
|
||||||
|
>
|
||||||
Cancel Training
|
Cancel Training
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@ -363,7 +351,9 @@ export default function ModelsPage() {
|
|||||||
<BrainCircuit className="h-8 w-8 text-primary" />
|
<BrainCircuit className="h-8 w-8 text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium">Ready to Train</h3>
|
<h3 className="font-medium">Ready to Train</h3>
|
||||||
<p className="text-sm text-muted-foreground">Configure your settings and start training</p>
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Configure your settings and start training
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -383,13 +373,18 @@ export default function ModelsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" className="flex-1" onClick={() => setActiveTab("my-models")}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => setActiveTab("my-models")}
|
||||||
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 gap-2"
|
className="flex-1 gap-2"
|
||||||
onClick={handleStartTraining}
|
onClick={handleStartTraining}
|
||||||
disabled={!selectedPipeline || !modelName}>
|
disabled={!selectedPipeline || !modelName}
|
||||||
|
>
|
||||||
<Play className="h-4 w-4" />
|
<Play className="h-4 w-4" />
|
||||||
Start Training
|
Start Training
|
||||||
</Button>
|
</Button>
|
||||||
@ -405,96 +400,3 @@ export default function ModelsPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModelCardProps {
|
|
||||||
model: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
hyperparameters: {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
dataSource: string;
|
|
||||||
status: string;
|
|
||||||
lastUpdated: string;
|
|
||||||
isSystem: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function ModelCard({ model }: ModelCardProps) {
|
|
||||||
return (
|
|
||||||
<Card className={model.isSystem ? "border-primary/20" : ""}>
|
|
||||||
<CardHeader className="pb-2">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<CardTitle className="text-lg">{model.name}</CardTitle>
|
|
||||||
<Badge variant={model.status === "active" ? "default" : "secondary"}>
|
|
||||||
{model.status === "active" ? "Active" : "Inactive"}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<CardDescription>{model.type} Model</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-1 mb-1">
|
|
||||||
<Database className="h-4 w-4 text-primary" />
|
|
||||||
<span className="text-sm font-medium">Data Source:</span>
|
|
||||||
</div>
|
|
||||||
{model.isSystem ? (
|
|
||||||
<div className="flex items-center gap-1 text-sm">
|
|
||||||
<Badge variant="outline" className="bg-primary/5">
|
|
||||||
System Base Model
|
|
||||||
</Badge>
|
|
||||||
<Info
|
|
||||||
className="h-4 w-4 text-muted-foreground cursor-help"
|
|
||||||
title="This is a pre-trained system model"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="text-sm">{model.dataSource}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-1 mb-1">
|
|
||||||
<Sliders className="h-4 w-4 text-primary" />
|
|
||||||
<span className="text-sm font-medium">Hyperparameters:</span>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-1">
|
|
||||||
{Object.entries(model.hyperparameters).map(([key, value]) => (
|
|
||||||
<div key={key} className="flex justify-between text-xs">
|
|
||||||
<span className="text-muted-foreground">{key}:</span>
|
|
||||||
<span>{value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center text-sm">
|
|
||||||
<Clock className="h-4 w-4 mr-2 text-muted-foreground" />
|
|
||||||
<span className="text-muted-foreground">Last updated:</span>
|
|
||||||
<span className="ml-1 font-medium">{model.lastUpdated}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex justify-between">
|
|
||||||
<Button variant="outline" size="sm" asChild>
|
|
||||||
<Link href={model.isSystem ? "/documentation/models" : "/models/details"}>View Details</Link>
|
|
||||||
</Button>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 text-primary border-primary/20 hover:border-primary">
|
|
||||||
<Settings className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
{!model.isSystem && (
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@plugin 'tailwindcss-animate';
|
@plugin 'tailwindcss-animate';
|
||||||
|
@plugin 'tailwind-scrollbar-hide';
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import type { Metadata } from "next";
|
|||||||
import { Poppins } from "next/font/google";
|
import { Poppins } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
import Sidebar from "@/components/sidebar";
|
|
||||||
|
|
||||||
const poppins = Poppins({
|
const poppins = Poppins({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
@ -26,7 +25,6 @@ export default function RootLayout({
|
|||||||
<body className={poppins.className}>
|
<body className={poppins.className}>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<Sidebar />
|
|
||||||
<div className="flex-1 overflow-auto">{children}</div>
|
<div className="flex-1 overflow-auto">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
195
frontend/components/map/analytics-panel.tsx
Normal file
195
frontend/components/map/analytics-panel.tsx
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { useModelState } from "@/store/model-store";
|
||||||
|
import {
|
||||||
|
BarChart2,
|
||||||
|
Clock,
|
||||||
|
Droplets,
|
||||||
|
LineChart,
|
||||||
|
Link,
|
||||||
|
MessageCircle,
|
||||||
|
Newspaper,
|
||||||
|
RefreshCw,
|
||||||
|
Wind,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useShallow } from "zustand/react/shallow";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Card, CardContent } from "../ui/card";
|
||||||
|
|
||||||
|
export function AnalyticsPanel({
|
||||||
|
setShowAnalytics,
|
||||||
|
}: {
|
||||||
|
setShowAnalytics: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const { selectedModel, setSelectedModel } = useModelState(
|
||||||
|
useShallow((state) => ({
|
||||||
|
selectedModel: state.selectedModel,
|
||||||
|
setSelectedModel: state.setSelectedModel,
|
||||||
|
models: state.models,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="absolute top-20 right-4 w-96 max-h-[800px] bg-background p-5 rounded-md overflow-y-auto z-20 map-overlay overflow-hidden scrollbar-hide cursor-grab">
|
||||||
|
<div className="map-overlay-header flex w-full">
|
||||||
|
<div className="flex justify-between w-full items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<BarChart2 className="h-5 w-5 text-primary" />
|
||||||
|
<span className="font-medium">Analytics</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 w-8 flex items-center justify-center"
|
||||||
|
onClick={() => setSelectedModel(selectedModel)}
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setShowAnalytics(false)}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="map-overlay-content">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Information in radius will be analyzed
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 mb-2">
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
Using: {selectedModel}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<Card className="mb-4">
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<LineChart className="h-4 w-4 text-primary" />
|
||||||
|
<span className="font-medium">Area Price History</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold mb-1">10,000,000 Baht</h3>
|
||||||
|
<p className="text-xs text-muted-foreground mb-3">
|
||||||
|
Overall Price History of this area
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="h-20 w-full relative">
|
||||||
|
{/* Simple line chart simulation */}
|
||||||
|
<div className="absolute bottom-0 left-0 w-full h-px bg-border"></div>
|
||||||
|
<div className="absolute bottom-0 left-0 h-full flex items-end">
|
||||||
|
<div className="w-1/6 h-8 border-b-2 border-r-2 border-primary rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-6 border-b-2 border-r-2 border-primary rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-7 border-b-2 border-r-2 border-primary rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-10 border-b-2 border-r-2 border-primary rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-12 border-b-2 border-r-2 border-primary rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-16 border-b-2 border-r-2 border-primary rounded-br"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="mb-4">
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<LineChart className="h-4 w-4 text-primary" />
|
||||||
|
<span className="font-medium">Price Prediction</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold mb-1">15,000,000 Baht</h3>
|
||||||
|
<p className="text-xs text-muted-foreground mb-3">
|
||||||
|
The estimated price based on various factors.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="h-20 w-full relative">
|
||||||
|
{/* Simple line chart simulation */}
|
||||||
|
<div className="absolute bottom-0 left-0 w-full h-px bg-border"></div>
|
||||||
|
<div className="absolute bottom-0 left-0 h-full flex items-end">
|
||||||
|
<div className="w-1/6 h-4 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-6 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-8 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-10 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-14 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
||||||
|
<div className="w-1/6 h-18 border-b-2 border-r-2 border-green-500 rounded-br"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2 mb-4">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-3">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Droplets className="h-5 w-5 text-blue-500 mb-1" />
|
||||||
|
<span className="text-sm font-medium">Flood Factor</span>
|
||||||
|
<Badge className="mt-1 bg-amber-500">Moderate</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-3">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<Wind className="h-5 w-5 text-purple-500 mb-1" />
|
||||||
|
<span className="text-sm font-medium">Air Factor</span>
|
||||||
|
<Badge className="mt-1 bg-destructive">Bad</Badge>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Local News Section */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<h4 className="font-medium text-sm mb-2 flex items-center">
|
||||||
|
<Newspaper className="h-4 w-4 mr-1 text-primary" />
|
||||||
|
Local News
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-3">
|
||||||
|
<h5 className="text-sm font-medium">
|
||||||
|
New BTS Extension Planned
|
||||||
|
</h5>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
The BTS Skytrain will be extended to cover more areas in
|
||||||
|
Sukhumvit by 2025.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center text-xs text-muted-foreground mt-1">
|
||||||
|
<Clock className="h-3 w-3 mr-1" />
|
||||||
|
<span>2 days ago</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-3">
|
||||||
|
<h5 className="text-sm font-medium">Property Tax Changes</h5>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
New property tax regulations will take effect next month
|
||||||
|
affecting luxury condominiums.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center text-xs text-muted-foreground mt-1">
|
||||||
|
<Clock className="h-3 w-3 mr-1" />
|
||||||
|
<span>1 week ago</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" className="w-full gap-2">
|
||||||
|
<MessageCircle className="h-4 w-4" />
|
||||||
|
Chat With AI
|
||||||
|
</Button>
|
||||||
|
<Link href="/price-prediction" className="flex-1">
|
||||||
|
<Button className="w-full">Full Analysis</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
97
frontend/components/map/chat-panel.tsx
Normal file
97
frontend/components/map/chat-panel.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { MessageCircle, Send, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
export function ChatPanel({
|
||||||
|
setShowChat,
|
||||||
|
}: {
|
||||||
|
setShowChat: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [messages, setMessages] = useState([
|
||||||
|
{ role: "assistant", content: "Hi! How can I help you today?" },
|
||||||
|
]);
|
||||||
|
const handleSendMessage = () => {
|
||||||
|
if (message.trim()) {
|
||||||
|
setMessages([...messages, { role: "user", content: message }]);
|
||||||
|
// Simulate AI response
|
||||||
|
setTimeout(() => {
|
||||||
|
setMessages((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content:
|
||||||
|
"I can provide information about properties in this area. Would you like to know about flood risks, air quality, or nearby amenities?",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, 1000);
|
||||||
|
setMessage("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="absolute top-20 right-4 w-96 h-[500px] map-overlay z-20 flex flex-col bg-background p-5 rounded-md scrollbar-hide cursor-grab">
|
||||||
|
<div className="map-overlay-header">
|
||||||
|
<div className="flex justify-between w-full items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MessageCircle className="h-5 w-5 text-primary" />
|
||||||
|
<span className="font-medium">Chat Assistant</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setShowChat(false)}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto p-3 space-y-3">
|
||||||
|
{messages.map((msg, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`flex ${
|
||||||
|
msg.role === "user" ? "justify-end" : "justify-start"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`max-w-[80%] rounded-lg px-3 py-2 ${
|
||||||
|
msg.role === "user"
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "bg-muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{msg.content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 border-t">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Type your message..."
|
||||||
|
className="flex-1 h-10 px-3 rounded-md border border-input bg-background"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") handleSendMessage();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="icon"
|
||||||
|
className="h-10 w-10"
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
>
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
175
frontend/components/map/filters-panel.tsx
Normal file
175
frontend/components/map/filters-panel.tsx
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { Filter, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Slider } from "../ui/slider";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||||
|
|
||||||
|
export function FiltersPanel({
|
||||||
|
setShowFilters,
|
||||||
|
}: {
|
||||||
|
setShowFilters: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const [activeTab, setActiveTab] = useState("basic");
|
||||||
|
const [priceRange, setPriceRange] = useState([5000000, 20000000]);
|
||||||
|
const [radius, setRadius] = useState(30);
|
||||||
|
return (
|
||||||
|
<div className="absolute top-20 right-4 w-96 map-overlay z-20 bg-background p-5 rounded-md overflow-y-auto scrollbar-hide cursor-grab">
|
||||||
|
<div className="map-overlay-header flex w-full">
|
||||||
|
<div className="flex justify-between w-full items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Filter className="h-5 w-5 text-primary" />
|
||||||
|
<span className="font-medium">Property Filters</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setShowFilters(false)}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
defaultValue="basic"
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={setActiveTab}
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
<TabsList className="w-full grid grid-cols-2">
|
||||||
|
<TabsTrigger value="basic">Basic</TabsTrigger>
|
||||||
|
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="basic" className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Area Radius
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Slider
|
||||||
|
defaultValue={[30]}
|
||||||
|
max={50}
|
||||||
|
min={1}
|
||||||
|
step={1}
|
||||||
|
onValueChange={(value) => setRadius(value[0])}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-sm w-16 text-right">{radius} km</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Time Period
|
||||||
|
</label>
|
||||||
|
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
||||||
|
<option value="all">All Time</option>
|
||||||
|
<option value="1m">Last Month</option>
|
||||||
|
<option value="3m">Last 3 Months</option>
|
||||||
|
<option value="6m">Last 6 Months</option>
|
||||||
|
<option value="1y">Last Year</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Property Type
|
||||||
|
</label>
|
||||||
|
<select className="w-full h-10 rounded-md border border-input bg-background px-3 py-2 text-sm">
|
||||||
|
<option value="any">Any Type</option>
|
||||||
|
<option value="house">House</option>
|
||||||
|
<option value="condo">Condominium</option>
|
||||||
|
<option value="townhouse">Townhouse</option>
|
||||||
|
<option value="land">Land</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="w-full">Apply Filters</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="advanced" className="p-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Price Range
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center justify-between text-xs text-muted-foreground mb-2">
|
||||||
|
<span>฿{priceRange[0].toLocaleString()}</span>
|
||||||
|
<span>฿{priceRange[1].toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
defaultValue={[5000000, 20000000]}
|
||||||
|
max={50000000}
|
||||||
|
min={1000000}
|
||||||
|
step={1000000}
|
||||||
|
onValueChange={(value) => setPriceRange(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Environmental Factors
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm">Low Flood Risk</span>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm">Good Air Quality</span>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm">Low Noise Pollution</span>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
Facilities Nearby
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="bts" className="h-4 w-4" />
|
||||||
|
<label htmlFor="bts" className="text-sm">
|
||||||
|
BTS/MRT Station
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="school" className="h-4 w-4" />
|
||||||
|
<label htmlFor="school" className="text-sm">
|
||||||
|
Schools
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="hospital" className="h-4 w-4" />
|
||||||
|
<label htmlFor="hospital" className="text-sm">
|
||||||
|
Hospitals
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="mall" className="h-4 w-4" />
|
||||||
|
<label htmlFor="mall" className="text-sm">
|
||||||
|
Shopping Malls
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="w-full">Apply Filters</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
frontend/components/map/map-with-search.tsx
Normal file
111
frontend/components/map/map-with-search.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { Loader } from "@googlemaps/js-api-loader";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
google: typeof google;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GOOGLE_MAPS_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!;
|
||||||
|
|
||||||
|
export default function MapWithSearch() {
|
||||||
|
const mapRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const mapInstanceRef = useRef<google.maps.Map | null>(null);
|
||||||
|
const [mapType, setMapType] = useState<string>("roadmap");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loader = new Loader({
|
||||||
|
apiKey: GOOGLE_MAPS_API_KEY,
|
||||||
|
version: "weekly",
|
||||||
|
libraries: ["places"],
|
||||||
|
});
|
||||||
|
|
||||||
|
loader.load().then(() => {
|
||||||
|
if (!mapRef.current || !inputRef.current) return;
|
||||||
|
|
||||||
|
const map = new google.maps.Map(mapRef.current, {
|
||||||
|
center: { lat: 13.7563, lng: 100.5018 },
|
||||||
|
zoom: 13,
|
||||||
|
gestureHandling: "greedy",
|
||||||
|
mapTypeControl: false,
|
||||||
|
mapTypeId: mapType,
|
||||||
|
});
|
||||||
|
|
||||||
|
mapInstanceRef.current = map;
|
||||||
|
|
||||||
|
const input = inputRef.current;
|
||||||
|
|
||||||
|
const searchBox = new google.maps.places.SearchBox(input);
|
||||||
|
|
||||||
|
map.addListener("bounds_changed", () => {
|
||||||
|
searchBox.setBounds(map.getBounds()!);
|
||||||
|
});
|
||||||
|
|
||||||
|
let marker: google.maps.Marker;
|
||||||
|
|
||||||
|
searchBox.addListener("places_changed", () => {
|
||||||
|
const places = searchBox.getPlaces();
|
||||||
|
if (!places || places.length === 0) return;
|
||||||
|
|
||||||
|
const place = places[0];
|
||||||
|
if (!place.geometry || !place.geometry.location) return;
|
||||||
|
|
||||||
|
map.panTo(place.geometry.location);
|
||||||
|
map.setZoom(15);
|
||||||
|
|
||||||
|
if (marker) marker.setMap(null);
|
||||||
|
marker = new google.maps.Marker({
|
||||||
|
map,
|
||||||
|
position: place.geometry.location,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mapInstanceRef.current) {
|
||||||
|
mapInstanceRef.current.setMapTypeId(mapType as google.maps.MapTypeId);
|
||||||
|
}
|
||||||
|
}, [mapType]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-row w-full h-full">
|
||||||
|
<div className="absolute mt-18 z-50 flex gap-2 rounded-md shadow-md">
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search locations..."
|
||||||
|
className="w-[300px]"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => setMapType(value as google.maps.MapTypeId)}
|
||||||
|
defaultValue="roadmap"
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[140px]">
|
||||||
|
<SelectValue placeholder="Map Type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="roadmap">Roadmap</SelectItem>
|
||||||
|
<SelectItem value="satellite">Satellite</SelectItem>
|
||||||
|
<SelectItem value="hybrid">Hybrid</SelectItem>
|
||||||
|
<SelectItem value="terrain">Terrain</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div ref={mapRef} className="w-full h-full rounded-md" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
134
frontend/components/map/property-info-panel.tsx
Normal file
134
frontend/components/map/property-info-panel.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import {
|
||||||
|
Bath,
|
||||||
|
BedDouble,
|
||||||
|
Building,
|
||||||
|
Droplets,
|
||||||
|
Home,
|
||||||
|
Link,
|
||||||
|
MapPin,
|
||||||
|
Star,
|
||||||
|
Sun,
|
||||||
|
Wind,
|
||||||
|
X,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
export function PropertyInfoPanel({
|
||||||
|
setShowPropertyInfo,
|
||||||
|
}: {
|
||||||
|
setShowPropertyInfo: (show: boolean) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="absolute top-20 right-4 w-96 map-overlay z-20 bg-background p-5 rounded-md overflow-y-auto scrollbar-hide cursor-grab">
|
||||||
|
<div className="map-overlay-header">
|
||||||
|
<div className="flex justify-between w-full items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Building className="h-5 w-5 text-primary" />
|
||||||
|
<span className="font-medium">Property Details</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
onClick={() => setShowPropertyInfo(false)}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="map-overlay-content mt-2">
|
||||||
|
<div className="relative mb-4">
|
||||||
|
<img
|
||||||
|
src="/map.png?height=200&width=400"
|
||||||
|
alt="Property"
|
||||||
|
className="w-full h-40 object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
<div className="absolute top-2 left-2 flex gap-1">
|
||||||
|
<Badge className="bg-primary">Condominium</Badge>
|
||||||
|
<Badge className="bg-amber-500">
|
||||||
|
<Star className="h-3 w-3 mr-1" /> Premium
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="font-medium text-lg mb-1">Modern Condominium</h3>
|
||||||
|
<div className="flex items-center text-muted-foreground text-sm mb-2">
|
||||||
|
<MapPin className="h-3.5 w-3.5 mr-1 flex-shrink-0" />
|
||||||
|
<span className="truncate">Sukhumvit, Bangkok</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-sm mb-3">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<BedDouble className="h-4 w-4 mr-1 text-primary" />
|
||||||
|
<span>3 Beds</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Bath className="h-4 w-4 mr-1 text-primary" />
|
||||||
|
<span>2 Baths</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Home className="h-4 w-4 mr-1 text-primary" />
|
||||||
|
<span>150 m²</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="font-semibold text-lg mb-4">฿15,000,000</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-sm mb-2">Environmental Factors</h4>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
<div className="flex flex-col items-center p-2 border rounded-md">
|
||||||
|
<Droplets className="h-5 w-5 text-blue-500 mb-1" />
|
||||||
|
<span className="text-xs font-medium">Flood Risk</span>
|
||||||
|
<Badge className="mt-1 text-xs bg-amber-500">Moderate</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center p-2 border rounded-md">
|
||||||
|
<Wind className="h-5 w-5 text-purple-500 mb-1" />
|
||||||
|
<span className="text-xs font-medium">Air Quality</span>
|
||||||
|
<Badge className="mt-1 text-xs bg-destructive">Poor</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center p-2 border rounded-md">
|
||||||
|
<Sun className="h-5 w-5 text-amber-500 mb-1" />
|
||||||
|
<span className="text-xs font-medium">Noise</span>
|
||||||
|
<Badge className="mt-1 text-xs bg-green-500">Low</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-medium text-sm mb-2">Nearby Facilities</h4>
|
||||||
|
<div className="space-y-1 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>BTS Phrom Phong</span>
|
||||||
|
<span className="text-muted-foreground">300m</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>EmQuartier Mall</span>
|
||||||
|
<span className="text-muted-foreground">500m</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Benchasiri Park</span>
|
||||||
|
<span className="text-muted-foreground">700m</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Link href="/properties/prop1" className="flex-1">
|
||||||
|
<Button className="w-full">View Details</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/price-prediction" className="flex-1">
|
||||||
|
<Button variant="outline" className="w-full">
|
||||||
|
Price Analysis
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
128
frontend/components/models/model-card.tsx
Normal file
128
frontend/components/models/model-card.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// import {
|
||||||
|
// ArrowRight,
|
||||||
|
// Clock,
|
||||||
|
// Database,
|
||||||
|
// Info,
|
||||||
|
// Link,
|
||||||
|
// Settings,
|
||||||
|
// Sliders,
|
||||||
|
// Trash2,
|
||||||
|
// } from "lucide-react";
|
||||||
|
// import { Badge } from "../ui/badge";
|
||||||
|
// import { Button } from "../ui/button";
|
||||||
|
// import {
|
||||||
|
// Card,
|
||||||
|
// CardContent,
|
||||||
|
// CardDescription,
|
||||||
|
// CardFooter,
|
||||||
|
// CardHeader,
|
||||||
|
// CardTitle,
|
||||||
|
// } from "../ui/card";
|
||||||
|
|
||||||
|
// interface ModelCardProps {
|
||||||
|
// model: {
|
||||||
|
// id: string;
|
||||||
|
// name: string;
|
||||||
|
// type: string;
|
||||||
|
// // hyperparameters: {
|
||||||
|
// // [key: string]: string;
|
||||||
|
// // };
|
||||||
|
// // dataSource: string;
|
||||||
|
// status: string;
|
||||||
|
// // lastUpdated: string;
|
||||||
|
// // isSystem: boolean;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function ModelCard({ model }: ModelCardProps) {
|
||||||
|
// return (
|
||||||
|
// <Card>
|
||||||
|
// <CardHeader className="pb-2">
|
||||||
|
// <div className="flex justify-between items-start">
|
||||||
|
// <CardTitle className="text-lg">{model.name}</CardTitle>
|
||||||
|
// <Badge variant={model.status === "active" ? "default" : "secondary"}>
|
||||||
|
// {model.status === "active" ? "Active" : "Inactive"}
|
||||||
|
// </Badge>
|
||||||
|
// </div>
|
||||||
|
// <CardDescription>{model.type} Model</CardDescription>
|
||||||
|
// </CardHeader>
|
||||||
|
// <CardContent>
|
||||||
|
// <div className="space-y-4">
|
||||||
|
// <div>
|
||||||
|
// <div className="flex items-center gap-1 mb-1">
|
||||||
|
// <Database className="h-4 w-4 text-primary" />
|
||||||
|
// <span className="text-sm font-medium">Data Source:</span>
|
||||||
|
// </div>
|
||||||
|
// {model.isSystem ? (
|
||||||
|
// <div className="flex items-center gap-1 text-sm">
|
||||||
|
// <Badge variant="outline" className="bg-primary/5">
|
||||||
|
// System Base Model
|
||||||
|
// </Badge>
|
||||||
|
// <span title="This is a pre-trained system model">
|
||||||
|
// <Info className="h-4 w-4 text-muted-foreground cursor-help" />
|
||||||
|
// </span>
|
||||||
|
// </div>
|
||||||
|
// ) : (
|
||||||
|
// <span className="text-sm">{model.dataSource}</span>
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div>
|
||||||
|
// <div className="flex items-center gap-1 mb-1">
|
||||||
|
// <Sliders className="h-4 w-4 text-primary" />
|
||||||
|
// <span className="text-sm font-medium">Hyperparameters:</span>
|
||||||
|
// </div>
|
||||||
|
// <div className="grid grid-cols-1 gap-1">
|
||||||
|
// {Object.entries(model.hyperparameters).map(([key, value]) => (
|
||||||
|
// <div key={key} className="flex justify-between text-xs">
|
||||||
|
// <span className="text-muted-foreground">{key}:</span>
|
||||||
|
// <span>{value}</span>
|
||||||
|
// </div>
|
||||||
|
// ))}
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div className="flex items-center text-sm">
|
||||||
|
// <Clock className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||||
|
// <span className="text-muted-foreground">Last updated:</span>
|
||||||
|
// <span className="ml-1 font-medium">{model.lastUpdated}</span>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </CardContent>
|
||||||
|
// <CardFooter className="flex justify-between">
|
||||||
|
// <Button variant="outline" size="sm" asChild>
|
||||||
|
// <Link
|
||||||
|
// href={model.isSystem ? "/documentation/models" : "/models/details"}
|
||||||
|
// >
|
||||||
|
// View Details
|
||||||
|
// </Link>
|
||||||
|
// </Button>
|
||||||
|
// <div className="flex gap-2">
|
||||||
|
// <Button
|
||||||
|
// variant="outline"
|
||||||
|
// size="icon"
|
||||||
|
// className="h-8 w-8 text-primary border-primary/20 hover:border-primary"
|
||||||
|
// >
|
||||||
|
// <Settings className="h-4 w-4" />
|
||||||
|
// </Button>
|
||||||
|
// {!model.isSystem && (
|
||||||
|
// <Button
|
||||||
|
// variant="outline"
|
||||||
|
// size="icon"
|
||||||
|
// className="h-8 w-8 border-primary/20 hover:border-primary"
|
||||||
|
// >
|
||||||
|
// <Trash2 className="h-4 w-4" />
|
||||||
|
// </Button>
|
||||||
|
// )}
|
||||||
|
// <Button
|
||||||
|
// variant="outline"
|
||||||
|
// size="icon"
|
||||||
|
// className="h-8 w-8 border-primary/20 hover:border-primary"
|
||||||
|
// >
|
||||||
|
// <ArrowRight className="h-4 w-4" />
|
||||||
|
// </Button>
|
||||||
|
// </div>
|
||||||
|
// </CardFooter>
|
||||||
|
// </Card>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
@ -14,17 +14,16 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTopNavigationStore } from "@/store/top-navgation-store";
|
import { useModelState } from "@/store/model-store";
|
||||||
import { useShallow } from "zustand/react/shallow";
|
import { useShallow } from "zustand/react/shallow";
|
||||||
|
|
||||||
export function TopNavigation() {
|
export function TopNavigation() {
|
||||||
const { selectedModel, setSelectedModel, models } = useTopNavigationStore(
|
const { selectedModel, setSelectedModel, models } = useModelState(
|
||||||
useShallow(
|
useShallow((state) => ({
|
||||||
(state) => ({
|
|
||||||
selectedModel: state.selectedModel,
|
selectedModel: state.selectedModel,
|
||||||
setSelectedModel: state.setSelectedModel,
|
setSelectedModel: state.setSelectedModel,
|
||||||
models: state.models,
|
models: state.models,
|
||||||
})),
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -33,18 +32,6 @@ export function TopNavigation() {
|
|||||||
<Home className="h-5 w-5" />
|
<Home className="h-5 w-5" />
|
||||||
<span className="font-semibold">BorBann</span>
|
<span className="font-semibold">BorBann</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex-1 max-w-md mx-4">
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search locations..."
|
|
||||||
className="w-full h-10 px-4 rounded-md border border-input bg-background"
|
|
||||||
/>
|
|
||||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
|
||||||
<MapPin className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
|
|||||||
210
frontend/components/pipeline/add-data-source.tsx
Normal file
210
frontend/components/pipeline/add-data-source.tsx
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import { DatabaseIcon, FileUp, Globe, Plus, Trash2 } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "../ui/accordion";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
|
type SourceType = "website" | "file" | "api";
|
||||||
|
|
||||||
|
type Source = {
|
||||||
|
id: string;
|
||||||
|
type: SourceType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AddDataSource() {
|
||||||
|
const [sources, setSources] = useState<Source[]>([
|
||||||
|
{ id: "source-1", type: "website" },
|
||||||
|
{ id: "source-2", type: "file" },
|
||||||
|
{ id: "source-3", type: "api" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const addSource = (type: SourceType) => {
|
||||||
|
const newId = `source-${Date.now()}`;
|
||||||
|
setSources((prev) => [...prev, { id: newId, type }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSource = (id: string) => {
|
||||||
|
setSources((prev) => prev.filter((source) => source.id !== id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSourceItem = (source: Source) => {
|
||||||
|
const commonProps = {
|
||||||
|
className: "border rounded-md mb-4 data-source-card",
|
||||||
|
value: source.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccordionItem key={source.id} {...commonProps}>
|
||||||
|
<AccordionTrigger className="px-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
{source.type === "website" && (
|
||||||
|
<Globe className="mr-2 h-5 w-5 text-primary" />
|
||||||
|
)}
|
||||||
|
{source.type === "file" && (
|
||||||
|
<FileUp className="mr-2 h-5 w-5 text-primary" />
|
||||||
|
)}
|
||||||
|
{source.type === "api" && (
|
||||||
|
<DatabaseIcon className="mr-2 h-5 w-5 text-primary" />
|
||||||
|
)}
|
||||||
|
<span>{`${
|
||||||
|
source.type.charAt(0).toUpperCase() + source.type.slice(1)
|
||||||
|
} Source`}</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="px-4 pb-4">
|
||||||
|
{source.type === "website" && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Website URL</Label>
|
||||||
|
<Input placeholder="https://example.com/listings" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label>Additional URLs (optional)</Label>
|
||||||
|
<Badge variant="outline" className="text-xs">
|
||||||
|
Pattern Detection
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
placeholder="https://example.com/page2 https://example.com/page3"
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-destructive"
|
||||||
|
onClick={() => removeSource(source.id)}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Remove Source
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{source.type === "file" && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Upload File</Label>
|
||||||
|
<div className="flex items-center justify-center p-6 border-2 border-dashed rounded-lg">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Drag and drop your file here, or click to browse
|
||||||
|
</p>
|
||||||
|
<Input type="file" className="mt-2 cursor-pointer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-destructive"
|
||||||
|
onClick={() => removeSource(source.id)}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Remove Source
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{source.type === "api" && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>API Endpoint URL</Label>
|
||||||
|
<Input placeholder="https://api.example.com/data" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Authentication Type</Label>
|
||||||
|
<select className="w-full border rounded-md px-3 py-2 text-sm">
|
||||||
|
<option value="none">None</option>
|
||||||
|
<option value="basic">Basic Auth</option>
|
||||||
|
<option value="bearer">Bearer Token</option>
|
||||||
|
<option value="api-key">API Key</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-destructive"
|
||||||
|
onClick={() => removeSource(source.id)}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Remove Source
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Data Sources</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Add one or more data sources to your pipeline
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Accordion
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
className="w-full"
|
||||||
|
defaultValue={sources[0]?.id}
|
||||||
|
>
|
||||||
|
{sources.map(renderSourceItem)}
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2 mt-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
className="w-full justify-start gap-2"
|
||||||
|
onClick={() => addSource("website")}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Add Website Source
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
className="w-full justify-start gap-2"
|
||||||
|
onClick={() => addSource("file")}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Add File Upload Source
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="button"
|
||||||
|
className="w-full justify-start gap-2"
|
||||||
|
onClick={() => addSource("api")}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Add API Source
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
77
frontend/components/pipeline/ai-assistant.tsx
Normal file
77
frontend/components/pipeline/ai-assistant.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
|
export function PipelineAiAssistant() {
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext();
|
||||||
|
return (
|
||||||
|
<Card className="mt-6 border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>AI Assistant</CardTitle>
|
||||||
|
<CardDescription>Customize how AI processes your data</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="ai-prompt">Additional Instructions for AI</Label>
|
||||||
|
<Textarea
|
||||||
|
id="ai-prompt"
|
||||||
|
placeholder="E.g., Focus on extracting pricing trends, ignore promotional content, prioritize property features..."
|
||||||
|
rows={4}
|
||||||
|
className="border-primary/20"
|
||||||
|
{...register("aiPrompt")}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Provide specific instructions to guide the AI in processing your
|
||||||
|
data sources
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/*
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="detect-fields">Auto-detect common fields</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Automatically identify price, location, etc.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch id="detect-fields" defaultChecked />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="suggest-mappings">Suggest field mappings</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Get AI suggestions for matching fields across sources
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch id="suggest-mappings" defaultChecked />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="deduplicate">Deduplicate records</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Remove duplicate entries automatically
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch id="deduplicate" defaultChecked />
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
frontend/components/pipeline/badge.tsx
Normal file
22
frontend/components/pipeline/badge.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
export function StatusBadge({
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
status: "active" | "paused" | "error";
|
||||||
|
}) {
|
||||||
|
if (status === "active") {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
variant="default"
|
||||||
|
className="bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700"
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
} else if (status === "paused") {
|
||||||
|
return <Badge variant="secondary">Paused</Badge>;
|
||||||
|
} else {
|
||||||
|
return <Badge variant="destructive">Error</Badge>;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
frontend/components/pipeline/card.tsx
Normal file
130
frontend/components/pipeline/card.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
Clock,
|
||||||
|
Copy,
|
||||||
|
Database,
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
|
RefreshCw,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { StatusBadge } from "./badge";
|
||||||
|
|
||||||
|
interface PipelineCardProps {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
status: "active" | "paused" | "error";
|
||||||
|
lastRun: string;
|
||||||
|
nextRun: string;
|
||||||
|
sources: number;
|
||||||
|
records: number;
|
||||||
|
error?: string;
|
||||||
|
aiPowered?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PipelineCard({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
status,
|
||||||
|
lastRun,
|
||||||
|
nextRun,
|
||||||
|
sources,
|
||||||
|
records,
|
||||||
|
error,
|
||||||
|
}: PipelineCardProps) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={`pipeline-card ${
|
||||||
|
status === "active"
|
||||||
|
? "border-2 border-green-500 dark:border-green-600"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<CardTitle className="text-lg">{name}</CardTitle>
|
||||||
|
<StatusBadge status={status} />
|
||||||
|
</div>
|
||||||
|
{/* <CardDescription>{description}</CardDescription> */}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<Clock className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||||
|
<span className="text-muted-foreground">Last run:</span>
|
||||||
|
<span className="ml-1 font-medium">{lastRun}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||||
|
<span className="text-muted-foreground">Next run:</span>
|
||||||
|
<span className="ml-1 font-medium">{nextRun}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<Database className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||||
|
<span className="text-muted-foreground">Sources:</span>
|
||||||
|
<span className="ml-1 font-medium">{sources}</span>
|
||||||
|
<span className="mx-2">•</span>
|
||||||
|
<span className="text-muted-foreground">Records:</span>
|
||||||
|
<span className="ml-1 font-medium">{records}</span>
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<div className="flex items-center text-sm text-destructive mt-2">
|
||||||
|
<AlertTriangle className="h-4 w-4 mr-2" />
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex justify-between">
|
||||||
|
<Link
|
||||||
|
href={`/data-pipeline/${name.toLowerCase().replace(/\s+/g, "-")}`}
|
||||||
|
>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
View Details
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 text-primary border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
{status === "active" ? (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<Pause className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<Play className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 border-primary/20 hover:border-primary"
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
frontend/components/pipeline/data-preview.tsx
Normal file
76
frontend/components/pipeline/data-preview.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
} from "../ui/card";
|
||||||
|
|
||||||
|
export function PipelineDataPreview() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Data Preview</CardTitle>
|
||||||
|
<CardDescription>Sample of the collected data</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="border rounded-md overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead className="bg-muted">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">ID</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Title
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Price
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Bedrooms
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Bathrooms
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Location
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Sq. Ft.
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y">
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 text-sm">P001</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Modern Apartment</td>
|
||||||
|
<td className="px-4 py-2 text-sm">$350,000</td>
|
||||||
|
<td className="px-4 py-2 text-sm">2</td>
|
||||||
|
<td className="px-4 py-2 text-sm">2</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Downtown</td>
|
||||||
|
<td className="px-4 py-2 text-sm">1,200</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 text-sm">P002</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Luxury Villa</td>
|
||||||
|
<td className="px-4 py-2 text-sm">$1,250,000</td>
|
||||||
|
<td className="px-4 py-2 text-sm">5</td>
|
||||||
|
<td className="px-4 py-2 text-sm">4</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Suburbs</td>
|
||||||
|
<td className="px-4 py-2 text-sm">3,500</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 text-sm">P003</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Cozy Studio</td>
|
||||||
|
<td className="px-4 py-2 text-sm">$180,000</td>
|
||||||
|
<td className="px-4 py-2 text-sm">1</td>
|
||||||
|
<td className="px-4 py-2 text-sm">1</td>
|
||||||
|
<td className="px-4 py-2 text-sm">City Center</td>
|
||||||
|
<td className="px-4 py-2 text-sm">650</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
246
frontend/components/pipeline/data-schema.tsx
Normal file
246
frontend/components/pipeline/data-schema.tsx
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import { Plus } from "lucide-react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
|
||||||
|
export function PipelineDataSchema() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<CardTitle>Data Schema & Field Management</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Customize fields detected from your data sources
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-sm font-medium">Detected Fields</h3>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
Refresh Detection
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border rounded-md p-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="field-mapping-item flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="field-title"
|
||||||
|
className="h-4 w-4 mr-3"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="field-title" className="font-medium">
|
||||||
|
Title
|
||||||
|
</Label>
|
||||||
|
<Badge className="ml-2 bg-green-500 text-white">
|
||||||
|
Auto-detected
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Property title or name
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
||||||
|
<option>String</option>
|
||||||
|
<option>Number</option>
|
||||||
|
<option>Boolean</option>
|
||||||
|
<option>Date</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="field-mapping-item flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="field-price"
|
||||||
|
className="h-4 w-4 mr-3"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="field-price" className="font-medium">
|
||||||
|
Price
|
||||||
|
</Label>
|
||||||
|
<Badge className="ml-2 bg-green-500 text-white">
|
||||||
|
Auto-detected
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Property price
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
||||||
|
<option>Number</option>
|
||||||
|
<option>String</option>
|
||||||
|
<option>Boolean</option>
|
||||||
|
<option>Date</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="field-mapping-item flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="field-location"
|
||||||
|
className="h-4 w-4 mr-3"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="field-location" className="font-medium">
|
||||||
|
Location
|
||||||
|
</Label>
|
||||||
|
<Badge className="ml-2 bg-green-500 text-white">
|
||||||
|
Auto-detected
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Property location
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
||||||
|
<option>String</option>
|
||||||
|
<option>Number</option>
|
||||||
|
<option>Boolean</option>
|
||||||
|
<option>Date</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="field-mapping-item flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="field-bedrooms"
|
||||||
|
className="h-4 w-4 mr-3"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="field-bedrooms" className="font-medium">
|
||||||
|
Bedrooms
|
||||||
|
</Label>
|
||||||
|
<Badge className="ml-2 bg-green-500 text-white">
|
||||||
|
Auto-detected
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Number of bedrooms
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
||||||
|
<option>Number</option>
|
||||||
|
<option>String</option>
|
||||||
|
<option>Boolean</option>
|
||||||
|
<option>Date</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="field-mapping-item flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="field-bathrooms"
|
||||||
|
className="h-4 w-4 mr-3"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="field-bathrooms" className="font-medium">
|
||||||
|
Bathrooms
|
||||||
|
</Label>
|
||||||
|
<Badge className="ml-2 bg-green-500 text-white">
|
||||||
|
Auto-detected
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Number of bathrooms
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<select className="h-8 rounded-md border border-input bg-background px-2 py-1 text-xs">
|
||||||
|
<option>Number</option>
|
||||||
|
<option>String</option>
|
||||||
|
<option>Boolean</option>
|
||||||
|
<option>Date</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="field-mapping-item flex items-center border-dashed">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="field-custom"
|
||||||
|
className="h-4 w-4 mr-3"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Input
|
||||||
|
placeholder="Add custom field"
|
||||||
|
className="border-none text-sm p-0 h-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button variant="ghost" size="sm" className="h-8 px-2">
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 mt-4">
|
||||||
|
<Label htmlFor="derived-fields">Derived Fields</Label>
|
||||||
|
<Card className="border border-dashed">
|
||||||
|
<CardHeader className="py-3">
|
||||||
|
<CardTitle className="text-sm">
|
||||||
|
Create calculated fields
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Use formulas to generate new fields from existing data
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="py-0">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="field-mapping-item">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="font-medium">
|
||||||
|
Price Per Square Foot
|
||||||
|
</Label>
|
||||||
|
<Badge variant="outline">Derived</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center mt-2">
|
||||||
|
<span className="text-xs text-muted-foreground mr-2">
|
||||||
|
Formula:
|
||||||
|
</span>
|
||||||
|
<code className="text-xs bg-muted/50 p-1 rounded">
|
||||||
|
price / squareFeet
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button variant="outline" size="sm" className="w-full">
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
Add Derived Field
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button variant="outline" className="gap-2 mr-2">
|
||||||
|
Reset to Default
|
||||||
|
</Button>
|
||||||
|
<Button>Save Field Configuration</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
frontend/components/pipeline/data-source.tsx
Normal file
48
frontend/components/pipeline/data-source.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "../ui/card";
|
||||||
|
|
||||||
|
export function PipelineDataSource() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Data Sources</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="p-3 border-2 rounded-md hover:border-highlight-border transition-all duration-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="font-medium">example-realty.com</span>
|
||||||
|
<Badge>Website</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Last updated: 2 hours ago
|
||||||
|
</p>
|
||||||
|
<p className="text-sm mt-1">540 records</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 border-2 rounded-md hover:border-highlight-border transition-all duration-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="font-medium">property-listings.com</span>
|
||||||
|
<Badge>Website</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Last updated: 2 hours ago
|
||||||
|
</p>
|
||||||
|
<p className="text-sm mt-1">420 records</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 border-2 rounded-md hover:border-highlight-border transition-all duration-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="font-medium">real-estate-api.com</span>
|
||||||
|
<Badge>API</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Last updated: 2 hours ago
|
||||||
|
</p>
|
||||||
|
<p className="text-sm mt-1">280 records</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
81
frontend/components/pipeline/details.tsx
Normal file
81
frontend/components/pipeline/details.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
|
export function PipelineDetails() {
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Pipeline Details</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Basic information about your data pipeline
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">Pipeline Name</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
placeholder="e.g., Property Listings Pipeline"
|
||||||
|
{...register("name")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.name && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{typeof errors.name?.message === "string"
|
||||||
|
? errors.name.message
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="description">Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
placeholder="Describe what this pipeline collects and how it will be used"
|
||||||
|
rows={4}
|
||||||
|
{...register("description")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{errors.description && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{typeof errors.description?.message === "string"
|
||||||
|
? errors.description.message
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="tags">Tags (optional)</Label>
|
||||||
|
<Input
|
||||||
|
id="tags"
|
||||||
|
placeholder="e.g., real-estate, properties, listings"
|
||||||
|
{...register("tags")}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Separate tags with commas
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
119
frontend/components/pipeline/export-data.tsx
Normal file
119
frontend/components/pipeline/export-data.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "@/components/ui/accordion";
|
||||||
|
import { Download } from "lucide-react";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "../ui/card";
|
||||||
|
|
||||||
|
export function PipelineExportData() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Export Options</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Accordion
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
className="w-full"
|
||||||
|
defaultValue="format-1"
|
||||||
|
>
|
||||||
|
<AccordionItem value="format-1" className="border-0">
|
||||||
|
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<AccordionTrigger className="py-1 px-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Download className="mr-2 h-4 w-4 text-primary" />
|
||||||
|
<span>Export as JSON</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pt-2 pb-1 px-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="pretty-json"
|
||||||
|
className="h-4 w-4"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<label htmlFor="pretty-json" className="text-sm">
|
||||||
|
Pretty print
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" className="w-full">
|
||||||
|
Download JSON
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="format-2" className="border-0">
|
||||||
|
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<AccordionTrigger className="py-1 px-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Download className="mr-2 h-4 w-4 text-primary" />
|
||||||
|
<span>Export as CSV</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pt-2 pb-1 px-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="include-headers"
|
||||||
|
className="h-4 w-4"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<label htmlFor="include-headers" className="text-sm">
|
||||||
|
Include headers
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" className="w-full">
|
||||||
|
Download CSV
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="format-3" className="border-0">
|
||||||
|
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<AccordionTrigger className="py-1 px-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Download className="mr-2 h-4 w-4 text-primary" />
|
||||||
|
<span>Export as SQLite</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pt-2 pb-1 px-2">
|
||||||
|
<Button size="sm" className="w-full">
|
||||||
|
Download SQLite
|
||||||
|
</Button>
|
||||||
|
</AccordionContent>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem value="format-4" className="border-0">
|
||||||
|
<div className="border rounded-md p-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<AccordionTrigger className="py-1 px-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Download className="mr-2 h-4 w-4 text-primary" />
|
||||||
|
<span>Export as YAML</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="pt-2 pb-1 px-2">
|
||||||
|
<Button size="sm" className="w-full">
|
||||||
|
Download YAML
|
||||||
|
</Button>
|
||||||
|
</AccordionContent>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
96
frontend/components/pipeline/output-config.tsx
Normal file
96
frontend/components/pipeline/output-config.tsx
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Check, Download } from "lucide-react";
|
||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
} from "../ui/card";
|
||||||
|
|
||||||
|
export function PipelineOutputConfig() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Output Configuration</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure how your data will be structured and exported
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>Output Format</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="border rounded-md p-3 data-source-card active">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="font-medium">JSON</span>
|
||||||
|
<Check className="h-4 w-4 text-primary" />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Structured data format
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border rounded-md p-3 data-source-card">
|
||||||
|
<span className="font-medium">CSV</span>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Spreadsheet compatible
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border rounded-md p-3 data-source-card">
|
||||||
|
<span className="font-medium">SQLite</span>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Portable database
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="border rounded-md p-3 data-source-card">
|
||||||
|
<span className="font-medium">YAML</span>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Human-readable format
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label>Format Preview</Label>
|
||||||
|
<Badge variant="outline">Sample Data</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="bg-muted/50 p-3 rounded-md overflow-x-auto">
|
||||||
|
<pre className="text-xs">
|
||||||
|
{`{
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "P001",
|
||||||
|
"title": "Modern Apartment",
|
||||||
|
"price": 350000,
|
||||||
|
"bedrooms": 2,
|
||||||
|
"location": "Downtown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "P002",
|
||||||
|
"title": "Luxury Villa",
|
||||||
|
"price": 1250000,
|
||||||
|
"bedrooms": 5,
|
||||||
|
"location": "Suburbs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
Export Data
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
frontend/components/pipeline/run-history.tsx
Normal file
109
frontend/components/pipeline/run-history.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
} from "../ui/card";
|
||||||
|
|
||||||
|
export function PipelineRunHistory() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Run History</CardTitle>
|
||||||
|
<CardDescription>History of pipeline executions</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="border rounded-md overflow-hidden">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead className="bg-muted">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Run ID
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Date
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Duration
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Records
|
||||||
|
</th>
|
||||||
|
<th className="px-4 py-2 text-left text-sm font-medium">
|
||||||
|
Actions
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y">
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 text-sm">RUN-123</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Today, 10:30 AM</td>
|
||||||
|
<td className="px-4 py-2 text-sm">
|
||||||
|
<Badge
|
||||||
|
variant="default"
|
||||||
|
className="bg-success hover:bg-success"
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm">2m 15s</td>
|
||||||
|
<td className="px-4 py-2 text-sm">1,240</td>
|
||||||
|
<td className="px-4 py-2 text-sm">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
View Log
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 text-sm">RUN-122</td>
|
||||||
|
<td className="px-4 py-2 text-sm">Yesterday, 10:30 AM</td>
|
||||||
|
<td className="px-4 py-2 text-sm">
|
||||||
|
<Badge
|
||||||
|
variant="default"
|
||||||
|
className="bg-success hover:bg-success"
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm">2m 10s</td>
|
||||||
|
<td className="px-4 py-2 text-sm">1,235</td>
|
||||||
|
<td className="px-4 py-2 text-sm">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
View Log
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="px-4 py-2 text-sm">RUN-121</td>
|
||||||
|
<td className="px-4 py-2 text-sm">2 days ago, 10:30 AM</td>
|
||||||
|
<td className="px-4 py-2 text-sm">
|
||||||
|
<Badge
|
||||||
|
variant="default"
|
||||||
|
className="bg-success hover:bg-success"
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-sm">2m 05s</td>
|
||||||
|
<td className="px-4 py-2 text-sm">1,228</td>
|
||||||
|
<td className="px-4 py-2 text-sm">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
View Log
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
167
frontend/components/pipeline/schedule-and-information.tsx
Normal file
167
frontend/components/pipeline/schedule-and-information.tsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
export function ScheduleAndInformation() {
|
||||||
|
return (
|
||||||
|
<Card className="mt-6 border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Schedule & Automation</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure when and how your pipeline should run
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="frequency">Run Frequency</Label>
|
||||||
|
<select
|
||||||
|
id="frequency"
|
||||||
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<option value="manual">Manual (Run on demand)</option>
|
||||||
|
<option value="hourly">Hourly</option>
|
||||||
|
<option value="daily">Daily</option>
|
||||||
|
<option value="weekly">Weekly</option>
|
||||||
|
<option value="monthly">Monthly</option>
|
||||||
|
<option value="custom">Custom Schedule</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="timezone">Timezone</Label>
|
||||||
|
<select
|
||||||
|
id="timezone"
|
||||||
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<option value="utc">UTC</option>
|
||||||
|
<option value="est">Eastern Time (ET)</option>
|
||||||
|
<option value="cst">Central Time (CT)</option>
|
||||||
|
<option value="mst">Mountain Time (MT)</option>
|
||||||
|
<option value="pst">Pacific Time (PT)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="max-records">Collection Limits</Label>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="limit-records"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label
|
||||||
|
htmlFor="limit-records"
|
||||||
|
className="text-sm font-normal"
|
||||||
|
>
|
||||||
|
Limit total records
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="max-records"
|
||||||
|
type="number"
|
||||||
|
placeholder="e.g., 1000"
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="stop-no-new"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
/>
|
||||||
|
<Label htmlFor="stop-no-new" className="text-sm font-normal">
|
||||||
|
Stop when no new records found
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="notifications">Notifications</Label>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="notify-complete"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor="notify-complete"
|
||||||
|
className="text-sm font-normal"
|
||||||
|
>
|
||||||
|
Notify when pipeline completes
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="notify-error"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<Label htmlFor="notify-error" className="text-sm font-normal">
|
||||||
|
Notify on errors
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Email for notifications"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="retry-settings">Retry Settings</Label>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label
|
||||||
|
htmlFor="retry-attempts"
|
||||||
|
className="text-sm font-normal"
|
||||||
|
>
|
||||||
|
Retry Attempts
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="retry-attempts"
|
||||||
|
type="number"
|
||||||
|
placeholder="e.g., 3"
|
||||||
|
defaultValue="3"
|
||||||
|
className="w-24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="retry-delay" className="text-sm font-normal">
|
||||||
|
Delay Between Retries (minutes)
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="retry-delay"
|
||||||
|
type="number"
|
||||||
|
placeholder="e.g., 5"
|
||||||
|
defaultValue="5"
|
||||||
|
className="w-24"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
100
frontend/components/pipeline/settings.tsx
Normal file
100
frontend/components/pipeline/settings.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
|
||||||
|
export function PipelineSettings() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Pipeline Settings</CardTitle>
|
||||||
|
<CardDescription>Configure pipeline behavior</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">Scheduling</h3>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="frequency">Run Frequency</Label>
|
||||||
|
<select
|
||||||
|
id="frequency"
|
||||||
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
defaultValue="daily"
|
||||||
|
>
|
||||||
|
<option value="manual">Manual (Run on demand)</option>
|
||||||
|
<option value="hourly">Hourly</option>
|
||||||
|
<option value="daily">Daily</option>
|
||||||
|
<option value="weekly">Weekly</option>
|
||||||
|
<option value="monthly">Monthly</option>
|
||||||
|
<option value="custom">Custom Schedule</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="time">Run Time</Label>
|
||||||
|
<Input id="time" type="time" defaultValue="09:00" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">Data Collection</h3>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="max-records">Maximum Records</Label>
|
||||||
|
<Input id="max-records" type="number" defaultValue="2000" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="retry-attempts">Retry Attempts</Label>
|
||||||
|
<Input id="retry-attempts" type="number" defaultValue="3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">Notifications</h3>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="notify-complete"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor="notify-complete"
|
||||||
|
className="text-sm font-normal"
|
||||||
|
>
|
||||||
|
Notify when pipeline completes
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="notify-error"
|
||||||
|
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
<Label htmlFor="notify-error" className="text-sm font-normal">
|
||||||
|
Notify on errors
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button>Save Settings</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
frontend/components/pipeline/status.tsx
Normal file
41
frontend/components/pipeline/status.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Badge } from "../ui/badge";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent } from "../ui/card";
|
||||||
|
|
||||||
|
export function PipelineStatus() {
|
||||||
|
return (
|
||||||
|
<Card className="border-2 border-green-500 dark:border-green-600">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg">Pipeline Status</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-muted-foreground">Status:</span>
|
||||||
|
<Badge
|
||||||
|
variant="default"
|
||||||
|
className="bg-green-500 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700"
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-muted-foreground">Last Run:</span>
|
||||||
|
<span>2 hours ago</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-muted-foreground">Next Run:</span>
|
||||||
|
<span>Tomorrow at 9:00 AM</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-muted-foreground">Run Frequency:</span>
|
||||||
|
<span>Daily</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-muted-foreground">Total Records:</span>
|
||||||
|
<span>1,240</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
118
frontend/components/pipeline/summary.tsx
Normal file
118
frontend/components/pipeline/summary.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
export const PipelineSummary = () => {
|
||||||
|
const { getValues } = useFormContext();
|
||||||
|
const values = getValues();
|
||||||
|
|
||||||
|
const tags = values.tags
|
||||||
|
? values.tags
|
||||||
|
.split(",")
|
||||||
|
.map((tag: string) => tag.trim()) // trim each tag
|
||||||
|
.filter(Boolean) // filter out empty strings
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="mt-6 border-0 hover:border-highlight-border transition-all duration-200">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Pipeline Summary</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
A quick overview of the pipeline configuration. Review before
|
||||||
|
launching.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 pt-6">
|
||||||
|
{/* details */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-semibold">Pipeline Details</h3>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<strong>Name:</strong> {values.name || "—"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Description:</strong> {values.description || "—"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Tags:</strong>
|
||||||
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
|
{tags.length > 0 ? (
|
||||||
|
tags.map((tag: string, index: number) => (
|
||||||
|
<Badge key={index} variant="outline">
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
No tags added
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* data sources */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-semibold">Data Sources</h3>
|
||||||
|
{values.dataSources && values.dataSources.length > 0 ? (
|
||||||
|
<ul className="list-disc pl-6 space-y-1">
|
||||||
|
{values.dataSources.map((src: string, index: number) => (
|
||||||
|
<li key={index}>{src}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
No data sources added.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* AI Assistant */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-semibold">AI Assistant</h3>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<strong>Prompt:</strong> {values.aiPrompt || "—"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Mode:</strong> {values.aiMode || "Default"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* schedule */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-semibold">Schedule</h3>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<strong>Frequency:</strong> {values.schedule || "—"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Start Date:</strong> {values.startDate || "—"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Timezone:</strong> {values.timezone || "—"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* form Inputs */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-semibold">Form Inputs</h3>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
No input fields added yet.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -2,10 +2,32 @@
|
|||||||
|
|
||||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||||
|
|
||||||
const Collapsible = CollapsiblePrimitive.Root
|
function Collapsible({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||||
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
function CollapsibleTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
|
||||||
|
return (
|
||||||
|
<CollapsiblePrimitive.CollapsibleTrigger
|
||||||
|
data-slot="collapsible-trigger"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
function CollapsibleContent({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
|
||||||
|
return (
|
||||||
|
<CollapsiblePrimitive.CollapsibleContent
|
||||||
|
data-slot="collapsible-content"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||||
|
|||||||
109
frontend/lib/api/pipelines/index.ts
Normal file
109
frontend/lib/api/pipelines/index.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { Pipeline, PipelineCreate, Run } from "./types";
|
||||||
|
|
||||||
|
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||||
|
|
||||||
|
// if (typeof window !== "undefined") {
|
||||||
|
// console.log(API_BASE);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// utility for handling fetch responses
|
||||||
|
async function handleResponse<T>(res: Response): Promise<T> {
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorBody = await res.json();
|
||||||
|
throw new Error(JSON.stringify(errorBody));
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /pipelines
|
||||||
|
export async function listPipelines(): Promise<Pipeline[]> {
|
||||||
|
const res = await fetch(`${API_BASE}/pipelines`);
|
||||||
|
return handleResponse<Pipeline[]>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /pipelines
|
||||||
|
export async function createPipeline(
|
||||||
|
payload: PipelineCreate
|
||||||
|
): Promise<Pipeline> {
|
||||||
|
const res = await fetch(`${API_BASE}/pipelines`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
return handleResponse<Pipeline>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /pipelines/{pipeline_id}
|
||||||
|
export async function getPipeline(pipeline_id: string): Promise<Pipeline> {
|
||||||
|
const res = await fetch(`${API_BASE}/pipelines/${pipeline_id}`);
|
||||||
|
return handleResponse<Pipeline>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /pipelines/{pipeline_id}/run
|
||||||
|
export async function runPipeline(pipeline_id: string): Promise<Run> {
|
||||||
|
const res = await fetch(`${API_BASE}/pipelines/${pipeline_id}/run`, {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
return handleResponse<Run>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /pipelines/{pipeline_id}/runs
|
||||||
|
export async function listRuns(pipeline_id: string): Promise<Run[]> {
|
||||||
|
const res = await fetch(`${API_BASE}/pipelines/${pipeline_id}/runs`);
|
||||||
|
return handleResponse<Run[]>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /pipelines/{pipeline_id}/runs/{run_id}
|
||||||
|
export async function getRun(
|
||||||
|
pipeline_id: string,
|
||||||
|
run_id: string
|
||||||
|
): Promise<Run> {
|
||||||
|
const res = await fetch(
|
||||||
|
`${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}`
|
||||||
|
);
|
||||||
|
return handleResponse<Run>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /pipelines/{pipeline_id}/runs/{run_id}/results
|
||||||
|
export async function getRunResults(
|
||||||
|
pipeline_id: string,
|
||||||
|
run_id: string
|
||||||
|
): Promise<any[]> {
|
||||||
|
const res = await fetch(
|
||||||
|
`${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}/results`
|
||||||
|
);
|
||||||
|
return handleResponse<any[]>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /pipelines/{pipeline_id}/runs/{run_id}/error
|
||||||
|
export async function getRunError(
|
||||||
|
pipeline_id: string,
|
||||||
|
run_id: string
|
||||||
|
): Promise<string> {
|
||||||
|
const res = await fetch(
|
||||||
|
`${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}/error`
|
||||||
|
);
|
||||||
|
return handleResponse<string>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSE: /pipelines/{pipeline_id}/runs/{run_id}/logs/stream
|
||||||
|
export function streamLogs(
|
||||||
|
pipeline_id: string,
|
||||||
|
run_id: string,
|
||||||
|
onMessage: (data: string) => void,
|
||||||
|
onError?: (event: Event) => void
|
||||||
|
): EventSource {
|
||||||
|
const url = `${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}/logs/stream`;
|
||||||
|
const eventSource = new EventSource(url);
|
||||||
|
|
||||||
|
eventSource.onmessage = (event) => {
|
||||||
|
onMessage(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = (event) => {
|
||||||
|
if (onError) onError(event);
|
||||||
|
eventSource.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
return eventSource;
|
||||||
|
}
|
||||||
64
frontend/lib/api/pipelines/types.ts
Normal file
64
frontend/lib/api/pipelines/types.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// types.ts
|
||||||
|
|
||||||
|
export interface ApiConfig {
|
||||||
|
url: string;
|
||||||
|
token?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiSource {
|
||||||
|
type: "api";
|
||||||
|
config: ApiConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileConfig {
|
||||||
|
path: string;
|
||||||
|
format?: "csv" | "json" | "sqlite";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileSource {
|
||||||
|
type: "file";
|
||||||
|
config: FileConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrapeConfig {
|
||||||
|
urls: string[];
|
||||||
|
schema_file?: string | null;
|
||||||
|
prompt?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrapeSource {
|
||||||
|
type: "scrape";
|
||||||
|
config: ScrapeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DataSource = ApiSource | FileSource | ScrapeSource;
|
||||||
|
|
||||||
|
export interface Pipeline {
|
||||||
|
id: string;
|
||||||
|
name?: string | null;
|
||||||
|
sources: DataSource[];
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipelineCreate {
|
||||||
|
name?: string | null;
|
||||||
|
sources: DataSource[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Run {
|
||||||
|
id: string;
|
||||||
|
pipeline_id: string;
|
||||||
|
status: "PENDING" | "RUNNING" | "COMPLETED" | "FAILED";
|
||||||
|
started_at: string;
|
||||||
|
finished_at?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValidationError {
|
||||||
|
loc: (string | number)[];
|
||||||
|
msg: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HTTPValidationError {
|
||||||
|
detail: ValidationError[];
|
||||||
|
}
|
||||||
10
frontend/lib/validations/pipeline.ts
Normal file
10
frontend/lib/validations/pipeline.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const pipelineSchema = z.object({
|
||||||
|
name: z.string().min(1, "Pipeline name is required"),
|
||||||
|
description: z.string().min(1, "Description is required"),
|
||||||
|
aiPrompt: z.string().optional(),
|
||||||
|
tags: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PipelineFormValues = z.infer<typeof pipelineSchema>;
|
||||||
@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@googlemaps/js-api-loader": "^1.16.8",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.3",
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.6",
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
||||||
@ -46,6 +47,7 @@
|
|||||||
"cmdk": "1.1.1",
|
"cmdk": "1.1.1",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"embla-carousel-react": "8.5.1",
|
"embla-carousel-react": "8.5.1",
|
||||||
|
"framer-motion": "^12.9.1",
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.487.0",
|
"lucide-react": "^0.487.0",
|
||||||
"next": "15.2.1",
|
"next": "15.2.1",
|
||||||
@ -53,12 +55,13 @@
|
|||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-day-picker": "8.10.1",
|
"react-day-picker": "8.10.1",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
|
"react-draggable": "^4.4.6",
|
||||||
"react-hook-form": "^7.54.1",
|
"react-hook-form": "^7.54.1",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^2.1.7",
|
||||||
"recharts": "^2.15.2",
|
"recharts": "^2.15.2",
|
||||||
"sonner": "^1.7.1",
|
"sonner": "^1.7.1",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwind-scrollbar-hide": "^2.0.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^0.9.6",
|
"vaul": "^0.9.6",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
@ -94,12 +97,13 @@
|
|||||||
"@radix-ui/react-toggle-group": "^1.1.1",
|
"@radix-ui/react-toggle-group": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "latest",
|
"@radix-ui/react-tooltip": "latest",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/google.maps": "^3.58.1",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "19.0.10",
|
"@types/react": "19.0.10",
|
||||||
"@types/react-dom": "19.0.4",
|
"@types/react-dom": "19.0.4",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.2.1",
|
"eslint-config-next": "15.2.1",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4.1.3",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': 19.0.4
|
'@types/react-dom': 19.0.4
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@googlemaps/js-api-loader':
|
||||||
|
specifier: ^1.16.8
|
||||||
|
version: 1.16.8
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^3.9.1
|
specifier: ^3.9.1
|
||||||
version: 3.9.1(react-hook-form@7.54.1)
|
version: 3.9.1(react-hook-form@7.54.1)
|
||||||
@ -116,6 +123,9 @@ dependencies:
|
|||||||
embla-carousel-react:
|
embla-carousel-react:
|
||||||
specifier: 8.5.1
|
specifier: 8.5.1
|
||||||
version: 8.5.1(react@19.0.0)
|
version: 8.5.1(react@19.0.0)
|
||||||
|
framer-motion:
|
||||||
|
specifier: ^12.9.1
|
||||||
|
version: 12.9.1(react-dom@19.0.0)(react@19.0.0)
|
||||||
input-otp:
|
input-otp:
|
||||||
specifier: 1.4.1
|
specifier: 1.4.1
|
||||||
version: 1.4.1(react-dom@19.0.0)(react@19.0.0)
|
version: 1.4.1(react-dom@19.0.0)(react@19.0.0)
|
||||||
@ -137,6 +147,9 @@ dependencies:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: 19.0.0
|
specifier: 19.0.0
|
||||||
version: 19.0.0(react@19.0.0)
|
version: 19.0.0(react@19.0.0)
|
||||||
|
react-draggable:
|
||||||
|
specifier: ^4.4.6
|
||||||
|
version: 4.4.6(react-dom@19.0.0)(react@19.0.0)
|
||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.54.1
|
specifier: ^7.54.1
|
||||||
version: 7.54.1(react@19.0.0)
|
version: 7.54.1(react@19.0.0)
|
||||||
@ -152,9 +165,9 @@ dependencies:
|
|||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.2.0
|
specifier: ^3.2.0
|
||||||
version: 3.2.0
|
version: 3.2.0
|
||||||
tailwindcss:
|
tailwind-scrollbar-hide:
|
||||||
specifier: ^4.1.3
|
specifier: ^2.0.0
|
||||||
version: 4.1.3
|
version: 2.0.0(tailwindcss@4.1.3)
|
||||||
tailwindcss-animate:
|
tailwindcss-animate:
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7(tailwindcss@4.1.3)
|
version: 1.0.7(tailwindcss@4.1.3)
|
||||||
@ -172,6 +185,9 @@ devDependencies:
|
|||||||
'@eslint/eslintrc':
|
'@eslint/eslintrc':
|
||||||
specifier: ^3
|
specifier: ^3
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
|
'@types/google.maps':
|
||||||
|
specifier: ^3.58.1
|
||||||
|
version: 3.58.1
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20
|
specifier: ^20
|
||||||
version: 20.0.0
|
version: 20.0.0
|
||||||
@ -181,6 +197,9 @@ devDependencies:
|
|||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 15.2.1
|
specifier: 15.2.1
|
||||||
version: 15.2.1(eslint@9.0.0)(typescript@5.0.2)
|
version: 15.2.1(eslint@9.0.0)(typescript@5.0.2)
|
||||||
|
tailwindcss:
|
||||||
|
specifier: ^4.1.3
|
||||||
|
version: 4.1.3
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5
|
specifier: ^5
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
@ -305,6 +324,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@googlemaps/js-api-loader@1.16.8:
|
||||||
|
resolution: {integrity: sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@hookform/resolvers@3.9.1(react-hook-form@7.54.1):
|
/@hookform/resolvers@3.9.1(react-hook-form@7.54.1):
|
||||||
resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==}
|
resolution: {integrity: sha512-ud2HqmGBM0P0IABqoskKWI6PEf6ZDDBZkFqe2Vnl+mTHCEHzr3ISjjZyCwTjC/qpL25JC9aIDkloQejvMeq0ug==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -644,8 +667,8 @@ packages:
|
|||||||
/@radix-ui/react-accordion@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-accordion@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A==}
|
resolution: {integrity: sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -672,8 +695,8 @@ packages:
|
|||||||
/@radix-ui/react-alert-dialog@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-alert-dialog@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==}
|
resolution: {integrity: sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -697,8 +720,8 @@ packages:
|
|||||||
/@radix-ui/react-arrow@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-arrow@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==}
|
resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -717,8 +740,8 @@ packages:
|
|||||||
/@radix-ui/react-aspect-ratio@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-aspect-ratio@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==}
|
resolution: {integrity: sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -737,8 +760,8 @@ packages:
|
|||||||
/@radix-ui/react-avatar@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-avatar@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==}
|
resolution: {integrity: sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -760,8 +783,8 @@ packages:
|
|||||||
/@radix-ui/react-checkbox@1.1.4(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-checkbox@1.1.4(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==}
|
resolution: {integrity: sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -787,8 +810,8 @@ packages:
|
|||||||
/@radix-ui/react-collapsible@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-collapsible@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==}
|
resolution: {integrity: sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -814,8 +837,8 @@ packages:
|
|||||||
/@radix-ui/react-collection@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-collection@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==}
|
resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -837,7 +860,7 @@ packages:
|
|||||||
/@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
|
resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -850,8 +873,8 @@ packages:
|
|||||||
/@radix-ui/react-context-menu@2.2.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-context-menu@2.2.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==}
|
resolution: {integrity: sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -875,7 +898,7 @@ packages:
|
|||||||
/@radix-ui/react-context@1.1.1(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-context@1.1.1(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -888,8 +911,8 @@ packages:
|
|||||||
/@radix-ui/react-dialog@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-dialog@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==}
|
resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -921,7 +944,7 @@ packages:
|
|||||||
/@radix-ui/react-direction@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-direction@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==}
|
resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -934,8 +957,8 @@ packages:
|
|||||||
/@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==}
|
resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -958,8 +981,8 @@ packages:
|
|||||||
/@radix-ui/react-dropdown-menu@2.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-dropdown-menu@2.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==}
|
resolution: {integrity: sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -984,7 +1007,7 @@ packages:
|
|||||||
/@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
|
resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -997,8 +1020,8 @@ packages:
|
|||||||
/@radix-ui/react-focus-scope@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-focus-scope@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==}
|
resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1019,8 +1042,8 @@ packages:
|
|||||||
/@radix-ui/react-hover-card@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-hover-card@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==}
|
resolution: {integrity: sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1047,7 +1070,7 @@ packages:
|
|||||||
/@radix-ui/react-id@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-id@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
|
resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1061,8 +1084,8 @@ packages:
|
|||||||
/@radix-ui/react-label@2.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-label@2.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==}
|
resolution: {integrity: sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1081,8 +1104,8 @@ packages:
|
|||||||
/@radix-ui/react-menu@2.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-menu@2.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==}
|
resolution: {integrity: sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1118,8 +1141,8 @@ packages:
|
|||||||
/@radix-ui/react-menubar@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-menubar@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg==}
|
resolution: {integrity: sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1147,8 +1170,8 @@ packages:
|
|||||||
/@radix-ui/react-navigation-menu@1.2.5(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-navigation-menu@1.2.5(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA==}
|
resolution: {integrity: sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1180,8 +1203,8 @@ packages:
|
|||||||
/@radix-ui/react-popover@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-popover@1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==}
|
resolution: {integrity: sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1214,8 +1237,8 @@ packages:
|
|||||||
/@radix-ui/react-popper@1.2.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-popper@1.2.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
|
resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1243,8 +1266,8 @@ packages:
|
|||||||
/@radix-ui/react-portal@1.1.4(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-portal@1.1.4(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==}
|
resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1264,8 +1287,8 @@ packages:
|
|||||||
/@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
|
resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1285,8 +1308,8 @@ packages:
|
|||||||
/@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-primitive@2.0.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
|
resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1305,8 +1328,8 @@ packages:
|
|||||||
/@radix-ui/react-progress@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-progress@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==}
|
resolution: {integrity: sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1326,8 +1349,8 @@ packages:
|
|||||||
/@radix-ui/react-radio-group@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-radio-group@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==}
|
resolution: {integrity: sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1355,8 +1378,8 @@ packages:
|
|||||||
/@radix-ui/react-roving-focus@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-roving-focus@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==}
|
resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1383,8 +1406,8 @@ packages:
|
|||||||
/@radix-ui/react-scroll-area@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-scroll-area@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==}
|
resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1411,8 +1434,8 @@ packages:
|
|||||||
/@radix-ui/react-select@2.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-select@2.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==}
|
resolution: {integrity: sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1451,8 +1474,8 @@ packages:
|
|||||||
/@radix-ui/react-separator@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-separator@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==}
|
resolution: {integrity: sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1471,8 +1494,8 @@ packages:
|
|||||||
/@radix-ui/react-slider@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-slider@1.2.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==}
|
resolution: {integrity: sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1501,7 +1524,7 @@ packages:
|
|||||||
/@radix-ui/react-slot@1.1.2(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-slot@1.1.2(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==}
|
resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1515,8 +1538,8 @@ packages:
|
|||||||
/@radix-ui/react-switch@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-switch@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==}
|
resolution: {integrity: sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1541,8 +1564,8 @@ packages:
|
|||||||
/@radix-ui/react-tabs@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-tabs@1.1.3(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==}
|
resolution: {integrity: sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1568,8 +1591,8 @@ packages:
|
|||||||
/@radix-ui/react-toast@1.2.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-toast@1.2.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==}
|
resolution: {integrity: sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1599,8 +1622,8 @@ packages:
|
|||||||
/@radix-ui/react-toggle-group@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-toggle-group@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==}
|
resolution: {integrity: sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1625,8 +1648,8 @@ packages:
|
|||||||
/@radix-ui/react-toggle@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-toggle@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==}
|
resolution: {integrity: sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1647,8 +1670,8 @@ packages:
|
|||||||
/@radix-ui/react-tooltip@1.1.8(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-tooltip@1.1.8(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==}
|
resolution: {integrity: sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1678,7 +1701,7 @@ packages:
|
|||||||
/@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1691,7 +1714,7 @@ packages:
|
|||||||
/@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
|
resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1705,7 +1728,7 @@ packages:
|
|||||||
/@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
|
resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1719,7 +1742,7 @@ packages:
|
|||||||
/@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
|
resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1732,7 +1755,7 @@ packages:
|
|||||||
/@radix-ui/react-use-previous@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-previous@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
|
resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1745,7 +1768,7 @@ packages:
|
|||||||
/@radix-ui/react-use-rect@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-rect@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
|
resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1759,7 +1782,7 @@ packages:
|
|||||||
/@radix-ui/react-use-size@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
/@radix-ui/react-use-size@1.1.0(@types/react@19.0.10)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
|
resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -1773,8 +1796,8 @@ packages:
|
|||||||
/@radix-ui/react-visually-hidden@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
/@radix-ui/react-visually-hidden@1.1.2(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0):
|
||||||
resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
|
resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
'@types/react-dom': '*'
|
'@types/react-dom': 19.0.4
|
||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -1997,6 +2020,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/google.maps@3.58.1:
|
||||||
|
resolution: {integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/json5@0.0.29:
|
/@types/json5@0.0.29:
|
||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2008,7 +2035,7 @@ packages:
|
|||||||
/@types/react-dom@19.0.4(@types/react@19.0.10):
|
/@types/react-dom@19.0.4(@types/react@19.0.10):
|
||||||
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
|
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^19.0.0
|
'@types/react': 19.0.10
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
dev: false
|
dev: false
|
||||||
@ -2534,6 +2561,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/clsx@1.2.1:
|
||||||
|
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/clsx@2.1.1:
|
/clsx@2.1.1:
|
||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -2567,6 +2599,7 @@ packages:
|
|||||||
|
|
||||||
/color-string@1.9.1:
|
/color-string@1.9.1:
|
||||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
simple-swizzle: 0.2.2
|
simple-swizzle: 0.2.2
|
||||||
@ -2576,6 +2609,7 @@ packages:
|
|||||||
/color@4.2.3:
|
/color@4.2.3:
|
||||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||||
engines: {node: '>=12.5.0'}
|
engines: {node: '>=12.5.0'}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
color-string: 1.9.1
|
color-string: 1.9.1
|
||||||
@ -3354,6 +3388,27 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/framer-motion@12.9.1(react-dom@19.0.0)(react@19.0.0):
|
||||||
|
resolution: {integrity: sha512-dZBp2TO0a39Cc24opshlLoM0/OdTZVKzcXWuhntfwy2Qgz3t9+N4sTyUqNANyHaRFiJUWbwwsXeDvQkEBPky+g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@emotion/is-prop-valid': '*'
|
||||||
|
react: ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^18.0.0 || ^19.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@emotion/is-prop-valid':
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
motion-dom: 12.9.1
|
||||||
|
motion-utils: 12.8.3
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
tslib: 2.8.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/function-bind@1.1.2:
|
/function-bind@1.1.2:
|
||||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -3560,6 +3615,7 @@ packages:
|
|||||||
|
|
||||||
/is-arrayish@0.3.2:
|
/is-arrayish@0.3.2:
|
||||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||||
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -4003,6 +4059,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/motion-dom@12.9.1:
|
||||||
|
resolution: {integrity: sha512-xqXEwRLDYDTzOgXobSoWtytRtGlf7zdkRfFbrrdP7eojaGQZ5Go4OOKtgnx7uF8sAkfr1ZjMvbCJSCIT2h6fkQ==}
|
||||||
|
dependencies:
|
||||||
|
motion-utils: 12.8.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/motion-utils@12.8.3:
|
||||||
|
resolution: {integrity: sha512-GYVauZEbca8/zOhEiYOY9/uJeedYQld6co/GJFKOy//0c/4lDqk0zB549sBYqqV2iMuX+uHrY1E5zd8A2L+1Lw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ms@2.1.3:
|
/ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -4283,6 +4349,18 @@ packages:
|
|||||||
scheduler: 0.25.0
|
scheduler: 0.25.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-draggable@4.4.6(react-dom@19.0.0)(react@19.0.0):
|
||||||
|
resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 16.3.0'
|
||||||
|
react-dom: '>= 16.3.0'
|
||||||
|
dependencies:
|
||||||
|
clsx: 1.2.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 19.0.0
|
||||||
|
react-dom: 19.0.0(react@19.0.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-hook-form@7.54.1(react@19.0.0):
|
/react-hook-form@7.54.1(react@19.0.0):
|
||||||
resolution: {integrity: sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==}
|
resolution: {integrity: sha512-PUNzFwQeQ5oHiiTUO7GO/EJXGEtuun2Y1A59rLnZBBj+vNEOWt/3ERTiG1/zt7dVeJEM+4vDX/7XQ/qanuvPMg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@ -4303,7 +4381,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -4319,7 +4397,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==}
|
resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -4361,7 +4439,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -4644,6 +4722,7 @@ packages:
|
|||||||
|
|
||||||
/simple-swizzle@0.2.2:
|
/simple-swizzle@0.2.2:
|
||||||
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||||
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish: 0.3.2
|
is-arrayish: 0.3.2
|
||||||
dev: false
|
dev: false
|
||||||
@ -4790,6 +4869,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
|
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tailwind-scrollbar-hide@2.0.0(tailwindcss@4.1.3):
|
||||||
|
resolution: {integrity: sha512-lqiIutHliEiODwBRHy4G2+Tcayo2U7+3+4frBmoMETD72qtah+XhOk5XcPzC1nJvXhXUdfl2ajlMhUc2qC6CIg==}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: '>=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20'
|
||||||
|
dependencies:
|
||||||
|
tailwindcss: 4.1.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tailwindcss-animate@1.0.7(tailwindcss@4.1.3):
|
/tailwindcss-animate@1.0.7(tailwindcss@4.1.3):
|
||||||
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4804,7 +4891,6 @@ packages:
|
|||||||
|
|
||||||
/tailwindcss@4.1.3:
|
/tailwindcss@4.1.3:
|
||||||
resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==}
|
resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/tapable@2.2.1:
|
/tapable@2.2.1:
|
||||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||||
@ -4969,7 +5055,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -4984,7 +5070,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': 19.0.10
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/react':
|
'@types/react':
|
||||||
@ -5108,7 +5194,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
|
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '>=18.0.0'
|
'@types/react': 19.0.10
|
||||||
immer: '>=9.0.6'
|
immer: '>=9.0.6'
|
||||||
react: '>=18.0.0'
|
react: '>=18.0.0'
|
||||||
use-sync-external-store: '>=1.2.0'
|
use-sync-external-store: '>=1.2.0'
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
type TopNavigationState = {
|
type ModelState = {
|
||||||
selectedModel: string;
|
selectedModel: string;
|
||||||
setSelectedModel: (model: string) => void;
|
setSelectedModel: (model: string) => void;
|
||||||
models: string[];
|
models: string[];
|
||||||
setModels: (models: string[]) => void;
|
setModels: (models: string[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTopNavigationStore = create<TopNavigationState>((set) => ({
|
export const useModelState = create<ModelState>((set) => ({
|
||||||
selectedModel: "Standard ML Model v2.4",
|
selectedModel: "Standard ML Model v2.4",
|
||||||
setSelectedModel: (model) => set({ selectedModel: model }),
|
setSelectedModel: (model) => set({ selectedModel: model }),
|
||||||
models: [
|
models: [
|
||||||
@ -26,9 +26,18 @@ export interface SuccessResponse {
|
|||||||
/**
|
/**
|
||||||
* Property Data Types
|
* Property Data Types
|
||||||
*/
|
*/
|
||||||
export type PropertyType = "Condominium" | "House" | "Townhouse" | "Land" | "Apartment" | "Other";
|
export type PropertyType =
|
||||||
|
| "Condominium"
|
||||||
|
| "House"
|
||||||
|
| "Townhouse"
|
||||||
|
| "Land"
|
||||||
|
| "Apartment"
|
||||||
|
| "Other";
|
||||||
export type OwnershipType = "Freehold" | "Leasehold";
|
export type OwnershipType = "Freehold" | "Leasehold";
|
||||||
export type FurnishingStatus = "Unfurnished" | "Partly Furnished" | "Fully Furnished";
|
export type FurnishingStatus =
|
||||||
|
| "Unfurnished"
|
||||||
|
| "Partly Furnished"
|
||||||
|
| "Fully Furnished";
|
||||||
|
|
||||||
export interface PriceHistoryEntry {
|
export interface PriceHistoryEntry {
|
||||||
date: string; // Consider using Date object after parsing
|
date: string; // Consider using Date object after parsing
|
||||||
@ -134,8 +143,19 @@ export interface DataPipeline {
|
|||||||
/**
|
/**
|
||||||
* Model Types
|
* Model Types
|
||||||
*/
|
*/
|
||||||
export type ModelType = "Regression" | "Neural Network" | "Geospatial" | "Time Series" | "Ensemble" | "Classification";
|
export type ModelType =
|
||||||
export type ModelStatus = "active" | "inactive" | "training" | "error" | "pending";
|
| "Regression"
|
||||||
|
| "Neural Network"
|
||||||
|
| "Geospatial"
|
||||||
|
| "Time Series"
|
||||||
|
| "Ensemble"
|
||||||
|
| "Classification";
|
||||||
|
export type ModelStatus =
|
||||||
|
| "active"
|
||||||
|
| "inactive"
|
||||||
|
| "training"
|
||||||
|
| "error"
|
||||||
|
| "pending";
|
||||||
|
|
||||||
export interface Model {
|
export interface Model {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user