xertica-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/App.tsx +182 -0
- package/README.md +330 -0
- package/assets/xertica-logo.svg +38 -0
- package/assets/xertica-x-logo.svg +21 -0
- package/bin/cli.ts +193 -0
- package/components/AssistenteXertica.tsx +2003 -0
- package/components/AudioPlayer.tsx +203 -0
- package/components/CodeBlock.tsx +242 -0
- package/components/DocumentEditor.tsx +504 -0
- package/components/ForgotPasswordPage.tsx +170 -0
- package/components/FormattedDocument.tsx +87 -0
- package/components/HomeContent.tsx +123 -0
- package/components/HomePage.tsx +70 -0
- package/components/LanguageSelector.tsx +54 -0
- package/components/LoginPage.tsx +199 -0
- package/components/MarkdownMessage.tsx +62 -0
- package/components/ModernChatInput.tsx +502 -0
- package/components/PodcastPlayer.tsx +409 -0
- package/components/ResetPasswordPage.tsx +234 -0
- package/components/Sidebar.tsx +489 -0
- package/components/TemplateContent.tsx +629 -0
- package/components/TemplatePage.tsx +70 -0
- package/components/ThemeToggle.tsx +65 -0
- package/components/VerifyEmailPage.tsx +187 -0
- package/components/XerticaLogo.tsx +69 -0
- package/components/XerticaOrbe.tsx +1339 -0
- package/components/XerticaXLogo.tsx +53 -0
- package/components/examples/DrawingMapExample.tsx +530 -0
- package/components/examples/FilterableMapExample.tsx +380 -0
- package/components/examples/LocationPickerExample.tsx +330 -0
- package/components/examples/MapExamples.tsx +280 -0
- package/components/examples/MapShowcase.tsx +446 -0
- package/components/examples/RouteMapExamples.tsx +329 -0
- package/components/examples/SimpleFilterableMap.tsx +192 -0
- package/components/examples/index.ts +52 -0
- package/components/figma/ImageWithFallback.tsx +27 -0
- package/components/index.ts +44 -0
- package/components/media/AudioPlayer.tsx +278 -0
- package/components/media/FloatingMediaWrapper.tsx +166 -0
- package/components/media/VideoPlayer.tsx +285 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +159 -0
- package/components/ui/alert.tsx +91 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +65 -0
- package/components/ui/badge.tsx +55 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button.tsx +78 -0
- package/components/ui/calendar.tsx +235 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +353 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +177 -0
- package/components/ui/context-menu.tsx +252 -0
- package/components/ui/dialog.tsx +138 -0
- package/components/ui/drawer.tsx +134 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/empty.tsx +90 -0
- package/components/ui/file-upload.tsx +152 -0
- package/components/ui/form.tsx +195 -0
- package/components/ui/google-maps-loader.tsx +379 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/index.ts +242 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +38 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/map-config.ts +12 -0
- package/components/ui/map-layers.tsx +129 -0
- package/components/ui/map.exports.ts +31 -0
- package/components/ui/map.tsx +412 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/navigation-menu.tsx +162 -0
- package/components/ui/notification-badge.tsx +61 -0
- package/components/ui/page-header.tsx +229 -0
- package/components/ui/pagination.tsx +127 -0
- package/components/ui/popover.tsx +48 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +56 -0
- package/components/ui/rating.tsx +102 -0
- package/components/ui/resizable.tsx +405 -0
- package/components/ui/route-map.tsx +246 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/search.tsx +70 -0
- package/components/ui/select.tsx +176 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +138 -0
- package/components/ui/sidebar.tsx +726 -0
- package/components/ui/simple-map.tsx +92 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/slider.tsx +58 -0
- package/components/ui/sonner.tsx +77 -0
- package/components/ui/stats-card.tsx +84 -0
- package/components/ui/stepper.tsx +126 -0
- package/components/ui/switch.tsx +34 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +66 -0
- package/components/ui/textarea.tsx +26 -0
- package/components/ui/timeline.tsx +140 -0
- package/components/ui/toggle-group.tsx +71 -0
- package/components/ui/toggle.tsx +46 -0
- package/components/ui/tooltip.tsx +61 -0
- package/components/ui/tree-view.tsx +123 -0
- package/components/ui/use-mobile.ts +24 -0
- package/components/ui/utils.ts +6 -0
- package/components/ui/xertica-assistant.tsx +1420 -0
- package/contexts/ApiKeyContext.tsx +123 -0
- package/contexts/AssistenteContext.tsx +118 -0
- package/contexts/BrandColorsContext.tsx +551 -0
- package/contexts/LanguageContext.tsx +36 -0
- package/contexts/ThemeContext.tsx +85 -0
- package/dist/cli.js +20922 -0
- package/eslint.config.js +41 -0
- package/guidelines/Guidelines.md +61 -0
- package/hooks/useTheme.ts +4 -0
- package/imports/Podcast.tsx +389 -0
- package/imports/XerticaAi.tsx +46 -0
- package/imports/XerticaX.tsx +20 -0
- package/imports/svg-aueiaqngck.ts +11 -0
- package/imports/svg-v9krss1ozd.ts +16 -0
- package/imports/svg-vhrdofe3qe.ts +5 -0
- package/index.css +4448 -0
- package/index.html +14 -0
- package/main.tsx +10 -0
- package/package.json +119 -0
- package/postcss.config.js +6 -0
- package/routes.tsx +33 -0
- package/styles/globals.css +15 -0
- package/styles/xertica/app-overrides/chat.css +61 -0
- package/styles/xertica/app-overrides/scrollbar.css +33 -0
- package/styles/xertica/base.css +70 -0
- package/styles/xertica/integrations/google-maps.css +76 -0
- package/styles/xertica/integrations/sonner.css +73 -0
- package/styles/xertica/theme-map.css +88 -0
- package/styles/xertica/tokens.css +190 -0
- package/tsconfig.json +31 -0
- package/tsconfig.node.json +10 -0
- package/utils/gemini.ts +140 -0
- package/vite-env.d.ts +12 -0
- package/vite.config.ts +36 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { RouteMap } from '../ui/route-map';
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
|
4
|
+
import { Badge } from '../ui/badge';
|
|
5
|
+
import { Button } from '../ui/button';
|
|
6
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
|
7
|
+
import { Navigation, Car, PersonStanding, Bike, Clock, MapPin } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* RouteMapExamples - Exemplos práticos de mapas com rotas
|
|
11
|
+
*
|
|
12
|
+
* Demonstra diferentes casos de uso para o componente RouteMap:
|
|
13
|
+
* - Rotas de entrega
|
|
14
|
+
* - Rotas turísticas
|
|
15
|
+
* - Diferentes modos de transporte
|
|
16
|
+
* - Rotas com múltiplos waypoints
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// 1. Rota de Entrega Simples
|
|
20
|
+
export function DeliveryRouteMap() {
|
|
21
|
+
const [routeInfo, setRouteInfo] = useState<{ distance: string; duration: string } | null>(null);
|
|
22
|
+
|
|
23
|
+
const origin = { lat: -23.5505, lng: -46.6333 }; // Avenida Paulista
|
|
24
|
+
const destination = { lat: -23.5613, lng: -46.6563 }; // Parque Ibirapuera
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Card>
|
|
28
|
+
<CardHeader>
|
|
29
|
+
<div className="flex items-center gap-2">
|
|
30
|
+
<Car className="w-5 h-5 text-primary" />
|
|
31
|
+
<CardTitle>Rota de Entrega</CardTitle>
|
|
32
|
+
</div>
|
|
33
|
+
<CardDescription>
|
|
34
|
+
Calcule o tempo e distância para sua entrega
|
|
35
|
+
</CardDescription>
|
|
36
|
+
</CardHeader>
|
|
37
|
+
<CardContent className="space-y-4">
|
|
38
|
+
<RouteMap
|
|
39
|
+
origin={origin}
|
|
40
|
+
destination={destination}
|
|
41
|
+
travelMode="DRIVING"
|
|
42
|
+
height="400px"
|
|
43
|
+
onRouteCalculated={(distance, duration) => {
|
|
44
|
+
setRouteInfo({ distance, duration });
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
{routeInfo && (
|
|
49
|
+
<div className="grid grid-cols-2 gap-3">
|
|
50
|
+
<div className="p-3 bg-muted rounded-lg">
|
|
51
|
+
<div className="flex items-center gap-2 mb-1">
|
|
52
|
+
<Navigation className="w-4 h-4 text-muted-foreground" />
|
|
53
|
+
<span className="text-xs text-muted-foreground">Distância</span>
|
|
54
|
+
</div>
|
|
55
|
+
<p className="text-lg font-semibold">{routeInfo.distance}</p>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="p-3 bg-muted rounded-lg">
|
|
58
|
+
<div className="flex items-center gap-2 mb-1">
|
|
59
|
+
<Clock className="w-4 h-4 text-muted-foreground" />
|
|
60
|
+
<span className="text-xs text-muted-foreground">Tempo</span>
|
|
61
|
+
</div>
|
|
62
|
+
<p className="text-lg font-semibold">{routeInfo.duration}</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</CardContent>
|
|
67
|
+
</Card>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Rota Turística com Múltiplos Pontos
|
|
72
|
+
export function TouristRouteMap() {
|
|
73
|
+
const [routeInfo, setRouteInfo] = useState<{ distance: string; duration: string } | null>(null);
|
|
74
|
+
|
|
75
|
+
const route = {
|
|
76
|
+
origin: { lat: -23.5505, lng: -46.6333 }, // Av. Paulista
|
|
77
|
+
destination: { lat: -23.5475, lng: -46.6361 }, // MASP
|
|
78
|
+
waypoints: [
|
|
79
|
+
{ lat: -23.5558, lng: -46.6396 }, // Parque Trianon
|
|
80
|
+
{ lat: -23.5506, lng: -46.6378 }, // Casa das Rosas
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Card>
|
|
86
|
+
<CardHeader>
|
|
87
|
+
<CardTitle>Rota Turística - Avenida Paulista</CardTitle>
|
|
88
|
+
<CardDescription>
|
|
89
|
+
Roteiro a pé pelos principais pontos turísticos
|
|
90
|
+
</CardDescription>
|
|
91
|
+
</CardHeader>
|
|
92
|
+
<CardContent className="space-y-4">
|
|
93
|
+
<RouteMap
|
|
94
|
+
origin={route.origin}
|
|
95
|
+
destination={route.destination}
|
|
96
|
+
waypoints={route.waypoints}
|
|
97
|
+
travelMode="WALKING"
|
|
98
|
+
height="450px"
|
|
99
|
+
onRouteCalculated={(distance, duration) => {
|
|
100
|
+
setRouteInfo({ distance, duration });
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<div className="space-y-2">
|
|
105
|
+
<h4 className="text-sm font-medium">Pontos da Rota:</h4>
|
|
106
|
+
<div className="space-y-1.5">
|
|
107
|
+
<div className="flex items-center gap-2 text-sm">
|
|
108
|
+
<Badge variant="outline" className="w-6 h-6 flex items-center justify-center p-0">A</Badge>
|
|
109
|
+
<span>Início - Avenida Paulista</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div className="flex items-center gap-2 text-sm">
|
|
112
|
+
<Badge variant="outline" className="w-6 h-6 flex items-center justify-center p-0">C</Badge>
|
|
113
|
+
<span>Parque Trianon</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="flex items-center gap-2 text-sm">
|
|
116
|
+
<Badge variant="outline" className="w-6 h-6 flex items-center justify-center p-0">D</Badge>
|
|
117
|
+
<span>Casa das Rosas</span>
|
|
118
|
+
</div>
|
|
119
|
+
<div className="flex items-center gap-2 text-sm">
|
|
120
|
+
<Badge variant="outline" className="w-6 h-6 flex items-center justify-center p-0">B</Badge>
|
|
121
|
+
<span>Fim - MASP</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{routeInfo && (
|
|
127
|
+
<div className="p-4 bg-green-50 dark:bg-green-950 rounded-lg border border-green-200 dark:border-green-800">
|
|
128
|
+
<div className="flex items-center justify-between">
|
|
129
|
+
<div className="flex items-center gap-2">
|
|
130
|
+
<PersonStanding className="w-5 h-5 text-green-600 dark:text-green-400" />
|
|
131
|
+
<span className="text-sm text-green-900 dark:text-green-100">
|
|
132
|
+
<strong>Caminhada:</strong> {routeInfo.distance} em {routeInfo.duration}
|
|
133
|
+
</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</CardContent>
|
|
139
|
+
</Card>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3. Comparação de Modos de Transporte
|
|
144
|
+
export function TransportModeComparison() {
|
|
145
|
+
const [selectedMode, setSelectedMode] = useState<'DRIVING' | 'WALKING' | 'BICYCLING'>('DRIVING');
|
|
146
|
+
const [routeInfo, setRouteInfo] = useState<{ distance: string; duration: string } | null>(null);
|
|
147
|
+
|
|
148
|
+
const origin = { lat: -23.5505, lng: -46.6333 };
|
|
149
|
+
const destination = { lat: -23.5613, lng: -46.6563 };
|
|
150
|
+
|
|
151
|
+
const modes = [
|
|
152
|
+
{ value: 'DRIVING' as const, label: 'Carro', icon: Car, color: 'blue' },
|
|
153
|
+
{ value: 'WALKING' as const, label: 'A Pé', icon: PersonStanding, color: 'green' },
|
|
154
|
+
{ value: 'BICYCLING' as const, label: 'Bicicleta', icon: Bike, color: 'orange' },
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Card>
|
|
159
|
+
<CardHeader>
|
|
160
|
+
<CardTitle>Comparação de Transportes</CardTitle>
|
|
161
|
+
<CardDescription>
|
|
162
|
+
Veja o tempo e distância para cada modo de transporte
|
|
163
|
+
</CardDescription>
|
|
164
|
+
</CardHeader>
|
|
165
|
+
<CardContent className="space-y-4">
|
|
166
|
+
<div className="flex gap-2">
|
|
167
|
+
{modes.map((mode) => {
|
|
168
|
+
const Icon = mode.icon;
|
|
169
|
+
return (
|
|
170
|
+
<Button
|
|
171
|
+
key={mode.value}
|
|
172
|
+
variant={selectedMode === mode.value ? 'default' : 'outline'}
|
|
173
|
+
size="sm"
|
|
174
|
+
onClick={() => setSelectedMode(mode.value)}
|
|
175
|
+
className="flex-1"
|
|
176
|
+
>
|
|
177
|
+
<Icon className="w-4 h-4 mr-2" />
|
|
178
|
+
{mode.label}
|
|
179
|
+
</Button>
|
|
180
|
+
);
|
|
181
|
+
})}
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<RouteMap
|
|
185
|
+
origin={origin}
|
|
186
|
+
destination={destination}
|
|
187
|
+
travelMode={selectedMode}
|
|
188
|
+
height="400px"
|
|
189
|
+
onRouteCalculated={(distance, duration) => {
|
|
190
|
+
setRouteInfo({ distance, duration });
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
|
|
194
|
+
{routeInfo && (
|
|
195
|
+
<div className="grid grid-cols-2 gap-3">
|
|
196
|
+
<div className="p-4 bg-muted rounded-lg text-center">
|
|
197
|
+
<p className="text-2xl font-bold">{routeInfo.distance}</p>
|
|
198
|
+
<p className="text-xs text-muted-foreground mt-1">Distância total</p>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="p-4 bg-muted rounded-lg text-center">
|
|
201
|
+
<p className="text-2xl font-bold">{routeInfo.duration}</p>
|
|
202
|
+
<p className="text-xs text-muted-foreground mt-1">Tempo estimado</p>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
</CardContent>
|
|
207
|
+
</Card>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 4. Rota de Entrega com Múltiplas Paradas
|
|
212
|
+
export function MultiStopDeliveryRoute() {
|
|
213
|
+
const [routeInfo, setRouteInfo] = useState<{ distance: string; duration: string } | null>(null);
|
|
214
|
+
|
|
215
|
+
const deliveryRoute = {
|
|
216
|
+
origin: { lat: -23.5505, lng: -46.6333 }, // Base
|
|
217
|
+
destination: { lat: -23.5505, lng: -46.6333 }, // Retorno à base
|
|
218
|
+
waypoints: [
|
|
219
|
+
{ lat: -23.5558, lng: -46.6500 }, // Cliente 1
|
|
220
|
+
{ lat: -23.5475, lng: -46.6361 }, // Cliente 2
|
|
221
|
+
{ lat: -23.5613, lng: -46.6563 }, // Cliente 3
|
|
222
|
+
]
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<Card>
|
|
227
|
+
<CardHeader>
|
|
228
|
+
<div className="flex items-center justify-between">
|
|
229
|
+
<div>
|
|
230
|
+
<CardTitle>Rota de Entrega - Múltiplas Paradas</CardTitle>
|
|
231
|
+
<CardDescription>
|
|
232
|
+
Otimize sua rota de entregas com paradas intermediárias
|
|
233
|
+
</CardDescription>
|
|
234
|
+
</div>
|
|
235
|
+
<Badge variant="secondary">
|
|
236
|
+
{deliveryRoute.waypoints.length} paradas
|
|
237
|
+
</Badge>
|
|
238
|
+
</div>
|
|
239
|
+
</CardHeader>
|
|
240
|
+
<CardContent className="space-y-4">
|
|
241
|
+
<RouteMap
|
|
242
|
+
origin={deliveryRoute.origin}
|
|
243
|
+
destination={deliveryRoute.destination}
|
|
244
|
+
waypoints={deliveryRoute.waypoints}
|
|
245
|
+
travelMode="DRIVING"
|
|
246
|
+
height="450px"
|
|
247
|
+
onRouteCalculated={(distance, duration) => {
|
|
248
|
+
setRouteInfo({ distance, duration });
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
|
|
252
|
+
<div className="space-y-3">
|
|
253
|
+
<div className="flex items-center justify-between p-3 bg-blue-50 dark:bg-blue-950 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
254
|
+
<div className="flex items-center gap-2">
|
|
255
|
+
<MapPin className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
256
|
+
<span className="text-sm text-blue-900 dark:text-blue-100">
|
|
257
|
+
<strong>Base Central</strong> → 3 entregas → Retorno
|
|
258
|
+
</span>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
{routeInfo && (
|
|
263
|
+
<div className="grid grid-cols-2 gap-3">
|
|
264
|
+
<div className="p-3 bg-muted rounded-lg">
|
|
265
|
+
<p className="text-sm text-muted-foreground mb-1">Distância Total</p>
|
|
266
|
+
<p className="text-xl font-semibold">{routeInfo.distance}</p>
|
|
267
|
+
</div>
|
|
268
|
+
<div className="p-3 bg-muted rounded-lg">
|
|
269
|
+
<p className="text-sm text-muted-foreground mb-1">Tempo Total</p>
|
|
270
|
+
<p className="text-xl font-semibold">{routeInfo.duration}</p>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
</CardContent>
|
|
276
|
+
</Card>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 5. Showcase Completo com Tabs
|
|
281
|
+
export function RouteMapShowcase() {
|
|
282
|
+
return (
|
|
283
|
+
<div className="space-y-6 max-w-6xl mx-auto">
|
|
284
|
+
<div className="space-y-2">
|
|
285
|
+
<h2 className="text-2xl">Route Maps</h2>
|
|
286
|
+
<p className="text-muted-foreground">
|
|
287
|
+
Exemplos de mapas com rotas calculadas automaticamente usando Google Maps Directions API
|
|
288
|
+
</p>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<Tabs defaultValue="delivery" className="space-y-4">
|
|
292
|
+
<TabsList className="grid w-full grid-cols-4">
|
|
293
|
+
<TabsTrigger value="delivery">
|
|
294
|
+
<Car className="w-4 h-4 mr-2" />
|
|
295
|
+
Entrega
|
|
296
|
+
</TabsTrigger>
|
|
297
|
+
<TabsTrigger value="tourist">
|
|
298
|
+
<PersonStanding className="w-4 h-4 mr-2" />
|
|
299
|
+
Turismo
|
|
300
|
+
</TabsTrigger>
|
|
301
|
+
<TabsTrigger value="transport">
|
|
302
|
+
<Bike className="w-4 h-4 mr-2" />
|
|
303
|
+
Comparação
|
|
304
|
+
</TabsTrigger>
|
|
305
|
+
<TabsTrigger value="multistop">
|
|
306
|
+
<MapPin className="w-4 h-4 mr-2" />
|
|
307
|
+
Multi-paradas
|
|
308
|
+
</TabsTrigger>
|
|
309
|
+
</TabsList>
|
|
310
|
+
|
|
311
|
+
<TabsContent value="delivery">
|
|
312
|
+
<DeliveryRouteMap />
|
|
313
|
+
</TabsContent>
|
|
314
|
+
|
|
315
|
+
<TabsContent value="tourist">
|
|
316
|
+
<TouristRouteMap />
|
|
317
|
+
</TabsContent>
|
|
318
|
+
|
|
319
|
+
<TabsContent value="transport">
|
|
320
|
+
<TransportModeComparison />
|
|
321
|
+
</TabsContent>
|
|
322
|
+
|
|
323
|
+
<TabsContent value="multistop">
|
|
324
|
+
<MultiStopDeliveryRoute />
|
|
325
|
+
</TabsContent>
|
|
326
|
+
</Tabs>
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Map } from '../ui/map';
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
|
4
|
+
import { Badge } from '../ui/badge';
|
|
5
|
+
import { Checkbox } from '../ui/checkbox';
|
|
6
|
+
import { Label } from '../ui/label';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Exemplo simples de mapa com marcadores de EMOJI e filtro
|
|
10
|
+
* Demonstra uso de emojis com fundo branco para contraste
|
|
11
|
+
* IMPORTANTE: Marcadores com emojis usam fundo branco (#FFFFFF)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Dados de exemplo com emojis e fundo branco
|
|
15
|
+
const locations = [
|
|
16
|
+
{
|
|
17
|
+
position: { lat: -23.5505, lng: -46.6333 },
|
|
18
|
+
label: '🍕',
|
|
19
|
+
title: 'Pizzaria do Bairro',
|
|
20
|
+
info: 'Melhor pizza artesanal',
|
|
21
|
+
color: '#FFFFFF', // Fundo branco para emojis
|
|
22
|
+
iconColor: '#000000', // Texto preto para emojis (melhor contraste)
|
|
23
|
+
category: 'food',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
position: { lat: -23.5485, lng: -46.6350 },
|
|
27
|
+
label: '🍣',
|
|
28
|
+
title: 'Sushi Premium',
|
|
29
|
+
info: 'Culinária japonesa autêntica',
|
|
30
|
+
color: '#FFFFFF',
|
|
31
|
+
iconColor: '#000000',
|
|
32
|
+
category: 'food',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
position: { lat: -23.5475, lng: -46.6361 },
|
|
36
|
+
label: '🏨',
|
|
37
|
+
title: 'Hotel Luxo',
|
|
38
|
+
info: '5 estrelas com spa',
|
|
39
|
+
color: '#FFFFFF',
|
|
40
|
+
iconColor: '#000000',
|
|
41
|
+
category: 'hotel',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
position: { lat: -23.5530, lng: -46.6340 },
|
|
45
|
+
label: '🏩',
|
|
46
|
+
title: 'Hotel Boutique',
|
|
47
|
+
info: 'Design contemporâneo',
|
|
48
|
+
color: '#FFFFFF',
|
|
49
|
+
iconColor: '#000000',
|
|
50
|
+
category: 'hotel',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
position: { lat: -23.5613, lng: -46.6563 },
|
|
54
|
+
label: '🌳',
|
|
55
|
+
title: 'Parque Ibirapuera',
|
|
56
|
+
info: 'Maior parque da cidade',
|
|
57
|
+
color: '#FFFFFF',
|
|
58
|
+
iconColor: '#000000',
|
|
59
|
+
category: 'attraction',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
position: { lat: -23.5558, lng: -46.6396 },
|
|
63
|
+
label: '🎨',
|
|
64
|
+
title: 'MASP',
|
|
65
|
+
info: 'Museu de Arte',
|
|
66
|
+
color: '#FFFFFF',
|
|
67
|
+
iconColor: '#000000',
|
|
68
|
+
category: 'attraction',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
position: { lat: -23.5465, lng: -46.6400 },
|
|
72
|
+
label: '🛍️',
|
|
73
|
+
title: 'Shopping Center',
|
|
74
|
+
info: 'Mais de 300 lojas',
|
|
75
|
+
color: '#FFFFFF',
|
|
76
|
+
iconColor: '#000000',
|
|
77
|
+
category: 'shopping',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
position: { lat: -23.5495, lng: -46.6345 },
|
|
81
|
+
label: '☕',
|
|
82
|
+
title: 'Café Artesanal',
|
|
83
|
+
info: 'Café especial e brunch',
|
|
84
|
+
color: '#FFFFFF',
|
|
85
|
+
iconColor: '#000000',
|
|
86
|
+
category: 'cafe',
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
export function SimpleFilterableMap() {
|
|
91
|
+
const [filters, setFilters] = useState({
|
|
92
|
+
food: true,
|
|
93
|
+
hotel: true,
|
|
94
|
+
attraction: true,
|
|
95
|
+
shopping: true,
|
|
96
|
+
cafe: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Filtrar marcadores
|
|
100
|
+
const filteredMarkers = locations
|
|
101
|
+
.filter(loc => filters[loc.category as keyof typeof filters])
|
|
102
|
+
.map(loc => ({
|
|
103
|
+
position: loc.position,
|
|
104
|
+
label: loc.label,
|
|
105
|
+
title: loc.title,
|
|
106
|
+
info: loc.info,
|
|
107
|
+
customColor: loc.color, // Sempre branco para emojis
|
|
108
|
+
iconColor: loc.iconColor, // Texto preto para emojis
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
const toggleFilter = (category: keyof typeof filters) => {
|
|
112
|
+
setFilters(prev => ({ ...prev, [category]: !prev[category] }));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const filterConfig = [
|
|
116
|
+
{ id: 'food', label: 'Restaurantes', emoji: '🍕', color: 'var(--destructive)', count: locations.filter(l => l.category === 'food').length },
|
|
117
|
+
{ id: 'hotel', label: 'Hotéis', emoji: '🏨', color: 'var(--info)', count: locations.filter(l => l.category === 'hotel').length },
|
|
118
|
+
{ id: 'attraction', label: 'Atrações', emoji: '🌳', color: 'var(--success)', count: locations.filter(l => l.category === 'attraction').length },
|
|
119
|
+
{ id: 'shopping', label: 'Compras', emoji: '🛍️', color: 'var(--warning)', count: locations.filter(l => l.category === 'shopping').length },
|
|
120
|
+
{ id: 'cafe', label: 'Cafés', emoji: '☕', color: 'var(--primary)', count: locations.filter(l => l.category === 'cafe').length },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<Card>
|
|
125
|
+
<CardHeader>
|
|
126
|
+
<div className="flex items-center justify-between">
|
|
127
|
+
<CardTitle>Mapa com Emojis</CardTitle>
|
|
128
|
+
<Badge variant="secondary">
|
|
129
|
+
{filteredMarkers.length} de {locations.length} locais
|
|
130
|
+
</Badge>
|
|
131
|
+
</div>
|
|
132
|
+
<CardDescription>
|
|
133
|
+
Marcadores com emojis em fundo branco - Selecione as categorias para visualizar
|
|
134
|
+
</CardDescription>
|
|
135
|
+
</CardHeader>
|
|
136
|
+
<CardContent className="space-y-4">
|
|
137
|
+
{/* Filtros Compactos com Checkboxes */}
|
|
138
|
+
<div className="flex flex-wrap gap-4 p-3 rounded-lg border bg-muted">
|
|
139
|
+
{filterConfig.map(filter => (
|
|
140
|
+
<div key={filter.id} className="flex items-center space-x-2">
|
|
141
|
+
<Checkbox
|
|
142
|
+
id={filter.id}
|
|
143
|
+
checked={filters[filter.id as keyof typeof filters]}
|
|
144
|
+
onCheckedChange={() => toggleFilter(filter.id as keyof typeof filters)}
|
|
145
|
+
/>
|
|
146
|
+
<Label htmlFor={filter.id} className="flex items-center gap-2 cursor-pointer text-sm">
|
|
147
|
+
<span className="text-base">{filter.emoji}</span>
|
|
148
|
+
<span>{filter.label}</span>
|
|
149
|
+
<Badge variant="outline" className="text-xs h-4 px-1">
|
|
150
|
+
{filter.count}
|
|
151
|
+
</Badge>
|
|
152
|
+
</Label>
|
|
153
|
+
</div>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{/* Mapa */}
|
|
158
|
+
<Map
|
|
159
|
+
center={{ lat: -23.5505, lng: -46.6333 }}
|
|
160
|
+
zoom={14}
|
|
161
|
+
markers={filteredMarkers}
|
|
162
|
+
height="450px"
|
|
163
|
+
zoomControl={true}
|
|
164
|
+
fullscreenControl={true}
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
{/* Legenda */}
|
|
168
|
+
<div className="flex flex-wrap gap-3 justify-center pt-3 border-t border-border">
|
|
169
|
+
{filterConfig.map(filter => (
|
|
170
|
+
<div
|
|
171
|
+
key={filter.id}
|
|
172
|
+
className="flex items-center gap-2 px-3 py-1 rounded-lg"
|
|
173
|
+
style={{
|
|
174
|
+
backgroundColor: filters[filter.id as keyof typeof filters]
|
|
175
|
+
? `color-mix(in srgb, ${filter.color}, transparent 85%)`
|
|
176
|
+
: 'var(--muted)',
|
|
177
|
+
opacity: filters[filter.id as keyof typeof filters] ? 1 : 0.5,
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<div
|
|
181
|
+
className="w-3 h-3 rounded-full"
|
|
182
|
+
style={{ backgroundColor: filter.color }}
|
|
183
|
+
/>
|
|
184
|
+
<span className="text-base">{filter.emoji}</span>
|
|
185
|
+
<span className="text-sm">{filter.label}</span>
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</CardContent>
|
|
190
|
+
</Card>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map Examples - Index
|
|
3
|
+
*
|
|
4
|
+
* Exportação centralizada de todos os exemplos de Map
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Exemplos básicos
|
|
8
|
+
export {
|
|
9
|
+
StoreDeliveryMap,
|
|
10
|
+
FavoritePlacesMap,
|
|
11
|
+
CompanyOfficesMap,
|
|
12
|
+
ServiceZoneMap,
|
|
13
|
+
CompactMapsGrid
|
|
14
|
+
} from './MapExamples';
|
|
15
|
+
|
|
16
|
+
// Exemplos de rotas
|
|
17
|
+
export {
|
|
18
|
+
DeliveryRouteMap,
|
|
19
|
+
TouristRouteMap,
|
|
20
|
+
TransportModeComparison,
|
|
21
|
+
MultiStopDeliveryRoute,
|
|
22
|
+
RouteMapShowcase
|
|
23
|
+
} from './RouteMapExamples';
|
|
24
|
+
|
|
25
|
+
// Exemplos avançados
|
|
26
|
+
export {
|
|
27
|
+
LocationPickerExample,
|
|
28
|
+
DeliveryAddressSelector
|
|
29
|
+
} from './LocationPickerExample';
|
|
30
|
+
|
|
31
|
+
// Exemplo com filtros
|
|
32
|
+
export {
|
|
33
|
+
FilterableMapExample,
|
|
34
|
+
markerGroups,
|
|
35
|
+
type MarkerGroupId
|
|
36
|
+
} from './FilterableMapExample';
|
|
37
|
+
|
|
38
|
+
// Exemplo simples com filtros
|
|
39
|
+
export { SimpleFilterableMap } from './SimpleFilterableMap';
|
|
40
|
+
|
|
41
|
+
// Showcase completo
|
|
42
|
+
export { MapShowcase } from './MapShowcase';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Uso:
|
|
46
|
+
*
|
|
47
|
+
* import { StoreDeliveryMap, LocationPickerExample } from '@/components/examples';
|
|
48
|
+
*
|
|
49
|
+
* ou
|
|
50
|
+
*
|
|
51
|
+
* import { StoreDeliveryMap } from '@/components/examples/MapExamples';
|
|
52
|
+
*/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const ERROR_IMG_SRC =
|
|
4
|
+
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg=='
|
|
5
|
+
|
|
6
|
+
export function ImageWithFallback(props: React.ImgHTMLAttributes<HTMLImageElement>) {
|
|
7
|
+
const [didError, setDidError] = useState(false)
|
|
8
|
+
|
|
9
|
+
const handleError = () => {
|
|
10
|
+
setDidError(true)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { src, alt, style, className, ...rest } = props
|
|
14
|
+
|
|
15
|
+
return didError ? (
|
|
16
|
+
<div
|
|
17
|
+
className={`inline-block bg-gray-100 text-center align-middle ${className ?? ''}`}
|
|
18
|
+
style={style}
|
|
19
|
+
>
|
|
20
|
+
<div className="flex items-center justify-center w-full h-full">
|
|
21
|
+
<img src={ERROR_IMG_SRC} alt="Error loading image" {...rest} data-original-url={src} />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
) : (
|
|
25
|
+
<img src={src} alt={alt} className={className} style={style} {...rest} onError={handleError} />
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Xertica Assistant Exports
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
export { XerticaAssistant } from './ui/xertica-assistant';
|
|
6
|
+
export type {
|
|
7
|
+
XerticaAssistantProps,
|
|
8
|
+
Message,
|
|
9
|
+
Conversation,
|
|
10
|
+
Suggestion,
|
|
11
|
+
SearchResult,
|
|
12
|
+
SearchSource,
|
|
13
|
+
SearchCommand,
|
|
14
|
+
MessageType,
|
|
15
|
+
AttachmentType,
|
|
16
|
+
SearchResultType,
|
|
17
|
+
AssistantMode,
|
|
18
|
+
AssistantTab
|
|
19
|
+
} from './ui/xertica-assistant';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Page Components
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
export { TemplatePage } from './TemplatePage';
|
|
26
|
+
|
|
27
|
+
// UI Components - All available via /components/ui/index.ts
|
|
28
|
+
export * from './ui';
|
|
29
|
+
|
|
30
|
+
// Utility Components (used by XerticaAssistant)
|
|
31
|
+
export { CodeBlock } from './CodeBlock';
|
|
32
|
+
export { MarkdownMessage } from './MarkdownMessage';
|
|
33
|
+
export { ModernChatInput } from './ModernChatInput';
|
|
34
|
+
export { FormattedDocument } from './FormattedDocument';
|
|
35
|
+
|
|
36
|
+
// Branding
|
|
37
|
+
export { XerticaLogo } from './XerticaLogo';
|
|
38
|
+
export { XerticaXLogo } from './XerticaXLogo';
|
|
39
|
+
export { XerticaOrbe } from './XerticaOrbe';
|
|
40
|
+
|
|
41
|
+
// Layout
|
|
42
|
+
export { Sidebar } from './Sidebar';
|
|
43
|
+
export { ThemeToggle } from './ThemeToggle';
|
|
44
|
+
export { LanguageSelector } from './LanguageSelector';
|