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,330 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Map } from '../ui/map';
|
|
3
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card';
|
|
4
|
+
import { Button } from '../ui/button';
|
|
5
|
+
import { Badge } from '../ui/badge';
|
|
6
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
|
7
|
+
import { MapPin, Store, Home, Building2, Navigation } from 'lucide-react';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* LocationPickerExample
|
|
11
|
+
*
|
|
12
|
+
* Exemplo avançado que demonstra:
|
|
13
|
+
* - Seleção de localização em um mapa
|
|
14
|
+
* - Múltiplas categorias de locais
|
|
15
|
+
* - Estado interativo
|
|
16
|
+
* - Integração com outros componentes UI
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface Location {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
address: string;
|
|
23
|
+
category: 'store' | 'office' | 'home';
|
|
24
|
+
coords: { lat: number; lng: number };
|
|
25
|
+
description: string;
|
|
26
|
+
isOpen?: boolean;
|
|
27
|
+
phone?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mockLocations: Location[] = [
|
|
31
|
+
{
|
|
32
|
+
id: '1',
|
|
33
|
+
name: 'Loja Centro',
|
|
34
|
+
address: 'Av. Paulista, 1000 - Bela Vista, São Paulo',
|
|
35
|
+
category: 'store',
|
|
36
|
+
coords: { lat: -23.5505, lng: -46.6333 },
|
|
37
|
+
description: 'Nossa loja principal no centro da cidade',
|
|
38
|
+
isOpen: true,
|
|
39
|
+
phone: '(11) 3456-7890'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: '2',
|
|
43
|
+
name: 'Loja Shopping',
|
|
44
|
+
address: 'Shopping Ibirapuera - Vila Clementino, São Paulo',
|
|
45
|
+
category: 'store',
|
|
46
|
+
coords: { lat: -23.5965, lng: -46.6520 },
|
|
47
|
+
description: 'Loja no Shopping Ibirapuera',
|
|
48
|
+
isOpen: true,
|
|
49
|
+
phone: '(11) 3456-7891'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: '3',
|
|
53
|
+
name: 'Escritório Administrativo',
|
|
54
|
+
address: 'Rua Augusta, 2000 - Consolação, São Paulo',
|
|
55
|
+
category: 'office',
|
|
56
|
+
coords: { lat: -23.5558, lng: -46.6600 },
|
|
57
|
+
description: 'Sede administrativa da empresa',
|
|
58
|
+
isOpen: false,
|
|
59
|
+
phone: '(11) 3456-7892'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: '4',
|
|
63
|
+
name: 'Centro de Distribuição',
|
|
64
|
+
address: 'Rodovia dos Bandeirantes, km 24',
|
|
65
|
+
category: 'office',
|
|
66
|
+
coords: { lat: -23.4850, lng: -46.6850 },
|
|
67
|
+
description: 'Centro de logística e distribuição',
|
|
68
|
+
isOpen: true,
|
|
69
|
+
phone: '(11) 3456-7893'
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export function LocationPickerExample() {
|
|
74
|
+
const [selectedLocation, setSelectedLocation] = useState<Location | null>(mockLocations[0]);
|
|
75
|
+
const [activeCategory, setActiveCategory] = useState<string>('all');
|
|
76
|
+
|
|
77
|
+
// Filtrar localizações por categoria
|
|
78
|
+
const filteredLocations = activeCategory === 'all'
|
|
79
|
+
? mockLocations
|
|
80
|
+
: mockLocations.filter(loc => loc.category === activeCategory);
|
|
81
|
+
|
|
82
|
+
// Criar marcadores para o mapa
|
|
83
|
+
const markers = filteredLocations.map(loc => ({
|
|
84
|
+
position: loc.coords,
|
|
85
|
+
title: loc.name,
|
|
86
|
+
info: loc.address,
|
|
87
|
+
label: loc.category === 'store' ? '🏪' : loc.category === 'office' ? '🏢' : '🏠'
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Calcular centro do mapa baseado nos locais filtrados
|
|
91
|
+
const mapCenter = selectedLocation?.coords || { lat: -23.5505, lng: -46.6333 };
|
|
92
|
+
|
|
93
|
+
const getCategoryIcon = (category: string) => {
|
|
94
|
+
switch (category) {
|
|
95
|
+
case 'store': return <Store className="w-4 h-4" />;
|
|
96
|
+
case 'office': return <Building2 className="w-4 h-4" />;
|
|
97
|
+
case 'home': return <Home className="w-4 h-4" />;
|
|
98
|
+
default: return <MapPin className="w-4 h-4" />;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getCategoryLabel = (category: string) => {
|
|
103
|
+
switch (category) {
|
|
104
|
+
case 'store': return 'Loja';
|
|
105
|
+
case 'office': return 'Escritório';
|
|
106
|
+
case 'home': return 'Residência';
|
|
107
|
+
default: return 'Local';
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="space-y-6 max-w-6xl mx-auto">
|
|
113
|
+
<div className="space-y-2">
|
|
114
|
+
<h2 className="text-2xl">Encontre uma Localização</h2>
|
|
115
|
+
<p className="text-muted-foreground">
|
|
116
|
+
Selecione um local para ver mais informações e como chegar
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<Tabs defaultValue="all" onValueChange={setActiveCategory}>
|
|
121
|
+
<TabsList className="w-full grid grid-cols-4">
|
|
122
|
+
<TabsTrigger value="all">
|
|
123
|
+
<MapPin className="w-4 h-4 mr-2" />
|
|
124
|
+
Todos ({mockLocations.length})
|
|
125
|
+
</TabsTrigger>
|
|
126
|
+
<TabsTrigger value="store">
|
|
127
|
+
<Store className="w-4 h-4 mr-2" />
|
|
128
|
+
Lojas ({mockLocations.filter(l => l.category === 'store').length})
|
|
129
|
+
</TabsTrigger>
|
|
130
|
+
<TabsTrigger value="office">
|
|
131
|
+
<Building2 className="w-4 h-4 mr-2" />
|
|
132
|
+
Escritórios ({mockLocations.filter(l => l.category === 'office').length})
|
|
133
|
+
</TabsTrigger>
|
|
134
|
+
<TabsTrigger value="home">
|
|
135
|
+
<Home className="w-4 h-4 mr-2" />
|
|
136
|
+
Outros ({mockLocations.filter(l => l.category === 'home').length})
|
|
137
|
+
</TabsTrigger>
|
|
138
|
+
</TabsList>
|
|
139
|
+
|
|
140
|
+
<TabsContent value={activeCategory} className="mt-6">
|
|
141
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
142
|
+
{/* Lista de Locais */}
|
|
143
|
+
<div className="lg:col-span-1 space-y-3">
|
|
144
|
+
<h3 className="text-sm font-medium text-muted-foreground">
|
|
145
|
+
{filteredLocations.length} {filteredLocations.length === 1 ? 'local' : 'locais'} encontrado{filteredLocations.length !== 1 ? 's' : ''}
|
|
146
|
+
</h3>
|
|
147
|
+
|
|
148
|
+
<div className="space-y-2 max-h-[600px] overflow-y-auto pr-2">
|
|
149
|
+
{filteredLocations.map((location) => (
|
|
150
|
+
<Card
|
|
151
|
+
key={location.id}
|
|
152
|
+
className={`cursor-pointer transition-all hover:shadow-md ${
|
|
153
|
+
selectedLocation?.id === location.id
|
|
154
|
+
? 'ring-2 ring-primary bg-primary/5'
|
|
155
|
+
: ''
|
|
156
|
+
}`}
|
|
157
|
+
onClick={() => setSelectedLocation(location)}
|
|
158
|
+
>
|
|
159
|
+
<CardHeader className="p-4">
|
|
160
|
+
<div className="flex items-start justify-between gap-2">
|
|
161
|
+
<div className="flex-1">
|
|
162
|
+
<CardTitle className="text-base flex items-center gap-2">
|
|
163
|
+
{getCategoryIcon(location.category)}
|
|
164
|
+
{location.name}
|
|
165
|
+
</CardTitle>
|
|
166
|
+
<CardDescription className="text-xs mt-1">
|
|
167
|
+
{location.address}
|
|
168
|
+
</CardDescription>
|
|
169
|
+
</div>
|
|
170
|
+
{location.isOpen !== undefined && (
|
|
171
|
+
<Badge
|
|
172
|
+
variant={location.isOpen ? 'success' : 'secondary'}
|
|
173
|
+
className="text-xs"
|
|
174
|
+
>
|
|
175
|
+
{location.isOpen ? 'Aberto' : 'Fechado'}
|
|
176
|
+
</Badge>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
</CardHeader>
|
|
180
|
+
</Card>
|
|
181
|
+
))}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Mapa e Detalhes */}
|
|
186
|
+
<div className="lg:col-span-2 space-y-4">
|
|
187
|
+
{/* Mapa */}
|
|
188
|
+
<Card>
|
|
189
|
+
<CardContent className="p-4">
|
|
190
|
+
<Map
|
|
191
|
+
center={mapCenter}
|
|
192
|
+
zoom={selectedLocation ? 15 : 12}
|
|
193
|
+
markers={markers}
|
|
194
|
+
height="400px"
|
|
195
|
+
zoomControl={true}
|
|
196
|
+
fullscreenControl={true}
|
|
197
|
+
/>
|
|
198
|
+
</CardContent>
|
|
199
|
+
</Card>
|
|
200
|
+
|
|
201
|
+
{/* Informações do Local Selecionado */}
|
|
202
|
+
{selectedLocation && (
|
|
203
|
+
<Card>
|
|
204
|
+
<CardHeader>
|
|
205
|
+
<div className="flex items-start justify-between">
|
|
206
|
+
<div className="space-y-1">
|
|
207
|
+
<div className="flex items-center gap-2">
|
|
208
|
+
{getCategoryIcon(selectedLocation.category)}
|
|
209
|
+
<CardTitle>{selectedLocation.name}</CardTitle>
|
|
210
|
+
</div>
|
|
211
|
+
<CardDescription>
|
|
212
|
+
{getCategoryLabel(selectedLocation.category)}
|
|
213
|
+
</CardDescription>
|
|
214
|
+
</div>
|
|
215
|
+
{selectedLocation.isOpen !== undefined && (
|
|
216
|
+
<Badge
|
|
217
|
+
variant={selectedLocation.isOpen ? 'success' : 'secondary'}
|
|
218
|
+
>
|
|
219
|
+
{selectedLocation.isOpen ? 'Aberto agora' : 'Fechado'}
|
|
220
|
+
</Badge>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
</CardHeader>
|
|
224
|
+
<CardContent className="space-y-4">
|
|
225
|
+
<div className="space-y-2">
|
|
226
|
+
<div className="flex items-start gap-2 text-sm">
|
|
227
|
+
<MapPin className="w-4 h-4 text-muted-foreground mt-0.5 shrink-0" />
|
|
228
|
+
<span>{selectedLocation.address}</span>
|
|
229
|
+
</div>
|
|
230
|
+
{selectedLocation.phone && (
|
|
231
|
+
<div className="flex items-center gap-2 text-sm">
|
|
232
|
+
<span className="text-muted-foreground">📞</span>
|
|
233
|
+
<span>{selectedLocation.phone}</span>
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<p className="text-sm text-muted-foreground">
|
|
239
|
+
{selectedLocation.description}
|
|
240
|
+
</p>
|
|
241
|
+
</CardContent>
|
|
242
|
+
<CardFooter className="flex gap-2">
|
|
243
|
+
<Button className="flex-1">
|
|
244
|
+
<Navigation className="w-4 h-4 mr-2" />
|
|
245
|
+
Como Chegar
|
|
246
|
+
</Button>
|
|
247
|
+
<Button variant="outline" className="flex-1">
|
|
248
|
+
Mais Informações
|
|
249
|
+
</Button>
|
|
250
|
+
</CardFooter>
|
|
251
|
+
</Card>
|
|
252
|
+
)}
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</TabsContent>
|
|
256
|
+
</Tabs>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* VARIAÇÃO: Seletor de Endereço de Entrega
|
|
263
|
+
*/
|
|
264
|
+
export function DeliveryAddressSelector() {
|
|
265
|
+
const [selectedAddress, setSelectedAddress] = useState<string | null>(null);
|
|
266
|
+
|
|
267
|
+
const addresses = [
|
|
268
|
+
{ id: '1', label: 'Casa', address: 'Rua das Flores, 123', coords: { lat: -23.5505, lng: -46.6333 } },
|
|
269
|
+
{ id: '2', label: 'Trabalho', address: 'Av. Paulista, 1000', coords: { lat: -23.5615, lng: -46.6560 } },
|
|
270
|
+
{ id: '3', label: 'Outro', address: 'Rua Augusta, 2000', coords: { lat: -23.5558, lng: -46.6600 } }
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
const selectedCoords = addresses.find(a => a.id === selectedAddress)?.coords || addresses[0].coords;
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<Card className="max-w-2xl">
|
|
277
|
+
<CardHeader>
|
|
278
|
+
<CardTitle>Selecione o Endereço de Entrega</CardTitle>
|
|
279
|
+
<CardDescription>
|
|
280
|
+
Escolha onde deseja receber seu pedido
|
|
281
|
+
</CardDescription>
|
|
282
|
+
</CardHeader>
|
|
283
|
+
<CardContent className="space-y-4">
|
|
284
|
+
<div className="space-y-2">
|
|
285
|
+
{addresses.map((addr) => (
|
|
286
|
+
<button
|
|
287
|
+
key={addr.id}
|
|
288
|
+
onClick={() => setSelectedAddress(addr.id)}
|
|
289
|
+
className={`w-full p-4 rounded-lg border-2 text-left transition-all ${
|
|
290
|
+
selectedAddress === addr.id
|
|
291
|
+
? 'border-primary bg-primary/5'
|
|
292
|
+
: 'border-border hover:border-primary/50'
|
|
293
|
+
}`}
|
|
294
|
+
>
|
|
295
|
+
<div className="flex items-start gap-3">
|
|
296
|
+
<Home className="w-5 h-5 text-muted-foreground mt-0.5" />
|
|
297
|
+
<div className="flex-1">
|
|
298
|
+
<p className="font-medium">{addr.label}</p>
|
|
299
|
+
<p className="text-sm text-muted-foreground">{addr.address}</p>
|
|
300
|
+
</div>
|
|
301
|
+
{selectedAddress === addr.id && (
|
|
302
|
+
<div className="w-5 h-5 rounded-full bg-primary flex items-center justify-center">
|
|
303
|
+
<span className="text-xs text-primary-foreground">✓</span>
|
|
304
|
+
</div>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
</button>
|
|
308
|
+
))}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<Map
|
|
312
|
+
center={selectedCoords}
|
|
313
|
+
zoom={15}
|
|
314
|
+
markers={[
|
|
315
|
+
{
|
|
316
|
+
position: selectedCoords,
|
|
317
|
+
title: "Endereço de Entrega"
|
|
318
|
+
}
|
|
319
|
+
]}
|
|
320
|
+
height="300px"
|
|
321
|
+
/>
|
|
322
|
+
</CardContent>
|
|
323
|
+
<CardFooter>
|
|
324
|
+
<Button className="w-full" disabled={!selectedAddress}>
|
|
325
|
+
Confirmar Endereço
|
|
326
|
+
</Button>
|
|
327
|
+
</CardFooter>
|
|
328
|
+
</Card>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import React 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 { Button } from '../ui/button';
|
|
6
|
+
import { MapPin, Navigation, Store, Heart } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Exemplos práticos de uso do componente Map
|
|
10
|
+
* Demonstra diferentes casos de uso do mundo real
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// 1. Mapa de Loja com Raio de Entrega
|
|
14
|
+
export function StoreDeliveryMap() {
|
|
15
|
+
const storeLocation = { lat: -23.5505, lng: -46.6333 };
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Card>
|
|
19
|
+
<CardHeader>
|
|
20
|
+
<div className="flex items-center gap-2">
|
|
21
|
+
<Store className="w-5 h-5 text-primary" />
|
|
22
|
+
<CardTitle>Área de Entrega</CardTitle>
|
|
23
|
+
</div>
|
|
24
|
+
<CardDescription>
|
|
25
|
+
Realizamos entregas em um raio de 5km da nossa loja
|
|
26
|
+
</CardDescription>
|
|
27
|
+
</CardHeader>
|
|
28
|
+
<CardContent>
|
|
29
|
+
<Map
|
|
30
|
+
center={storeLocation}
|
|
31
|
+
zoom={13}
|
|
32
|
+
markers={[
|
|
33
|
+
{
|
|
34
|
+
position: storeLocation,
|
|
35
|
+
title: "Nossa Loja",
|
|
36
|
+
info: "Avenida Paulista, 1000"
|
|
37
|
+
}
|
|
38
|
+
]}
|
|
39
|
+
circle={{
|
|
40
|
+
center: storeLocation,
|
|
41
|
+
radius: 5000,
|
|
42
|
+
fillColor: "#10B981",
|
|
43
|
+
strokeColor: "#059669"
|
|
44
|
+
}}
|
|
45
|
+
height="400px"
|
|
46
|
+
/>
|
|
47
|
+
<div className="mt-4 flex items-center justify-between">
|
|
48
|
+
<div className="flex items-center gap-2">
|
|
49
|
+
<MapPin className="w-4 h-4 text-muted-foreground" />
|
|
50
|
+
<span className="text-sm text-muted-foreground">
|
|
51
|
+
Cobertura de 5km
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
<Button size="sm">
|
|
55
|
+
<Navigation className="w-4 h-4 mr-2" />
|
|
56
|
+
Como Chegar
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
59
|
+
</CardContent>
|
|
60
|
+
</Card>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Mapa de Locais Favoritos
|
|
65
|
+
export function FavoritePlacesMap() {
|
|
66
|
+
const favorites = [
|
|
67
|
+
{
|
|
68
|
+
position: { lat: -23.5505, lng: -46.6333 },
|
|
69
|
+
label: "⭐",
|
|
70
|
+
title: "Restaurante Favorito",
|
|
71
|
+
info: "Melhor pizza da cidade!"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
position: { lat: -23.5475, lng: -46.6361 },
|
|
75
|
+
label: "☕",
|
|
76
|
+
title: "Cafeteria",
|
|
77
|
+
info: "Café artesanal"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
position: { lat: -23.5613, lng: -46.6563 },
|
|
81
|
+
label: "🏃",
|
|
82
|
+
title: "Parque",
|
|
83
|
+
info: "Local de corrida matinal"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
position: { lat: -23.5558, lng: -46.6396 },
|
|
87
|
+
label: "🎬",
|
|
88
|
+
title: "Cinema",
|
|
89
|
+
info: "Cinema independente"
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Card>
|
|
95
|
+
<CardHeader>
|
|
96
|
+
<div className="flex items-center justify-between">
|
|
97
|
+
<div className="flex items-center gap-2">
|
|
98
|
+
<Heart className="w-5 h-5 text-destructive" />
|
|
99
|
+
<CardTitle>Meus Lugares Favoritos</CardTitle>
|
|
100
|
+
</div>
|
|
101
|
+
<Badge variant="secondary">{favorites.length} locais</Badge>
|
|
102
|
+
</div>
|
|
103
|
+
<CardDescription>
|
|
104
|
+
Lugares que você marcou como favoritos
|
|
105
|
+
</CardDescription>
|
|
106
|
+
</CardHeader>
|
|
107
|
+
<CardContent>
|
|
108
|
+
<Map
|
|
109
|
+
center={{ lat: -23.5505, lng: -46.6333 }}
|
|
110
|
+
zoom={13}
|
|
111
|
+
markers={favorites}
|
|
112
|
+
height="450px"
|
|
113
|
+
/>
|
|
114
|
+
</CardContent>
|
|
115
|
+
</Card>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 3. Mapa de Escritórios da Empresa
|
|
120
|
+
export function CompanyOfficesMap() {
|
|
121
|
+
const offices = [
|
|
122
|
+
{
|
|
123
|
+
position: { lat: -23.5505, lng: -46.6333 },
|
|
124
|
+
label: "HQ",
|
|
125
|
+
title: "Sede - São Paulo",
|
|
126
|
+
info: "Matriz da empresa - 200 funcionários"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
position: { lat: -22.9068, lng: -43.1729 },
|
|
130
|
+
label: "RJ",
|
|
131
|
+
title: "Filial - Rio de Janeiro",
|
|
132
|
+
info: "Escritório regional - 80 funcionários"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
position: { lat: -19.9167, lng: -43.9345 },
|
|
136
|
+
label: "BH",
|
|
137
|
+
title: "Filial - Belo Horizonte",
|
|
138
|
+
info: "Centro de distribuição - 50 funcionários"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
position: { lat: -25.4284, lng: -49.2733 },
|
|
142
|
+
label: "CWB",
|
|
143
|
+
title: "Filial - Curitiba",
|
|
144
|
+
info: "Centro de tecnologia - 120 funcionários"
|
|
145
|
+
}
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Card>
|
|
150
|
+
<CardHeader>
|
|
151
|
+
<CardTitle>Nossos Escritórios</CardTitle>
|
|
152
|
+
<CardDescription>
|
|
153
|
+
Presença em 4 estados brasileiros
|
|
154
|
+
</CardDescription>
|
|
155
|
+
</CardHeader>
|
|
156
|
+
<CardContent>
|
|
157
|
+
<Map
|
|
158
|
+
center={{ lat: -22.5, lng: -45.0 }}
|
|
159
|
+
zoom={6}
|
|
160
|
+
markers={offices}
|
|
161
|
+
height="500px"
|
|
162
|
+
zoomControl={true}
|
|
163
|
+
fullscreenControl={true}
|
|
164
|
+
/>
|
|
165
|
+
<div className="mt-4 grid grid-cols-2 md:grid-cols-4 gap-2">
|
|
166
|
+
{offices.map((office, index) => (
|
|
167
|
+
<div key={index} className="text-center p-3 bg-muted rounded-lg">
|
|
168
|
+
<div className="text-2xl mb-1">{office.label}</div>
|
|
169
|
+
<p className="text-xs text-muted-foreground line-clamp-1">
|
|
170
|
+
{office.title}
|
|
171
|
+
</p>
|
|
172
|
+
</div>
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
</CardContent>
|
|
176
|
+
</Card>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 4. Mapa de Zona de Serviço com Polígono
|
|
181
|
+
export function ServiceZoneMap() {
|
|
182
|
+
const serviceZone = {
|
|
183
|
+
paths: [
|
|
184
|
+
{ lat: -23.540, lng: -46.620 },
|
|
185
|
+
{ lat: -23.540, lng: -46.660 },
|
|
186
|
+
{ lat: -23.565, lng: -46.660 },
|
|
187
|
+
{ lat: -23.565, lng: -46.620 }
|
|
188
|
+
],
|
|
189
|
+
fillColor: "#3B82F6",
|
|
190
|
+
strokeColor: "#2563EB"
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<Card>
|
|
195
|
+
<CardHeader>
|
|
196
|
+
<CardTitle>Área de Atendimento</CardTitle>
|
|
197
|
+
<CardDescription>
|
|
198
|
+
Zona azul indica nossa área de serviço prioritária
|
|
199
|
+
</CardDescription>
|
|
200
|
+
</CardHeader>
|
|
201
|
+
<CardContent>
|
|
202
|
+
<Map
|
|
203
|
+
center={{ lat: -23.5525, lng: -46.640 }}
|
|
204
|
+
zoom={13}
|
|
205
|
+
polygon={serviceZone}
|
|
206
|
+
markers={[
|
|
207
|
+
{
|
|
208
|
+
position: { lat: -23.5525, lng: -46.640 },
|
|
209
|
+
title: "Central de Operações",
|
|
210
|
+
info: "Base de atendimento"
|
|
211
|
+
}
|
|
212
|
+
]}
|
|
213
|
+
height="450px"
|
|
214
|
+
/>
|
|
215
|
+
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
216
|
+
<p className="text-sm text-blue-900 dark:text-blue-100">
|
|
217
|
+
<strong>Atendimento prioritário:</strong> Clientes dentro da zona azul
|
|
218
|
+
recebem atendimento em até 2 horas.
|
|
219
|
+
</p>
|
|
220
|
+
</div>
|
|
221
|
+
</CardContent>
|
|
222
|
+
</Card>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 5. Grid de Mapas Compactos (Comparação)
|
|
227
|
+
export function CompactMapsGrid() {
|
|
228
|
+
const cities = [
|
|
229
|
+
{
|
|
230
|
+
name: "São Paulo",
|
|
231
|
+
center: { lat: -23.5505, lng: -46.6333 },
|
|
232
|
+
description: "Maior cidade do Brasil"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "Rio de Janeiro",
|
|
236
|
+
center: { lat: -22.9068, lng: -43.1729 },
|
|
237
|
+
description: "Cidade maravilhosa"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "Brasília",
|
|
241
|
+
center: { lat: -15.7942, lng: -47.8822 },
|
|
242
|
+
description: "Capital federal"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "Salvador",
|
|
246
|
+
center: { lat: -12.9714, lng: -38.5014 },
|
|
247
|
+
description: "Primeira capital"
|
|
248
|
+
}
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<Card>
|
|
253
|
+
<CardHeader>
|
|
254
|
+
<CardTitle>Principais Cidades</CardTitle>
|
|
255
|
+
<CardDescription>
|
|
256
|
+
Visão rápida das principais metrópoles brasileiras
|
|
257
|
+
</CardDescription>
|
|
258
|
+
</CardHeader>
|
|
259
|
+
<CardContent>
|
|
260
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
261
|
+
{cities.map((city, index) => (
|
|
262
|
+
<div key={index} className="space-y-2">
|
|
263
|
+
<div className="flex items-center justify-between">
|
|
264
|
+
<h4 className="font-medium">{city.name}</h4>
|
|
265
|
+
<Badge variant="outline">{city.description}</Badge>
|
|
266
|
+
</div>
|
|
267
|
+
<Map
|
|
268
|
+
center={city.center}
|
|
269
|
+
zoom={11}
|
|
270
|
+
height="200px"
|
|
271
|
+
disableDefaultUI={true}
|
|
272
|
+
gestureHandling="none"
|
|
273
|
+
/>
|
|
274
|
+
</div>
|
|
275
|
+
))}
|
|
276
|
+
</div>
|
|
277
|
+
</CardContent>
|
|
278
|
+
</Card>
|
|
279
|
+
);
|
|
280
|
+
}
|