198 lines
4.8 KiB
TypeScript
198 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
} from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Trash2, MilestoneIcon, Clock } from "lucide-react";
|
|
import { Location } from "@/types/types";
|
|
import getOsmEmbedUrl from "@/lib/osmEmbed";
|
|
import LocationDialog from "./location-dialog";
|
|
import openNavigationApp from "@/lib/openNavigationMap";
|
|
import formatLocation from "@/lib/formatLocation";
|
|
|
|
interface LocationCardProps {
|
|
location: Location;
|
|
onUpdate?: (location: Location) => void;
|
|
onDelete?: (location: Location) => void;
|
|
canUpdate?: boolean;
|
|
canDelete?: boolean;
|
|
}
|
|
|
|
export const LocationCard: React.FC<LocationCardProps> = ({
|
|
location,
|
|
onUpdate,
|
|
onDelete,
|
|
canDelete = false,
|
|
canUpdate = false,
|
|
}) => {
|
|
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
|
|
|
|
const handleDelete = () => {
|
|
onDelete?.(location);
|
|
setIsDialogOpen(false);
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full max-w-md">
|
|
<CardHeader className="flex flex-row justify-between align-middle">
|
|
<div>
|
|
<CardTitle>{location.street}</CardTitle>
|
|
<CardDescription>
|
|
{location.city}, {location.postalCode}
|
|
</CardDescription>
|
|
</div>
|
|
<Button
|
|
onClick={() => openNavigationApp(formatLocation(location))}
|
|
className="m-0"
|
|
>
|
|
<MilestoneIcon />
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* OSM Embed Map */}
|
|
{location.latitude && location.longitude && (
|
|
<div className="h-[200px] w-full rounded-lg border overflow-hidden">
|
|
<iframe
|
|
width="100%"
|
|
height="100%"
|
|
src={getOsmEmbedUrl(
|
|
location.latitude,
|
|
location.longitude,
|
|
)}
|
|
title="OpenStreetMap Preview"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-2 overflow-y-auto">
|
|
{location.events?.slice(0, 3).map((event) => (
|
|
<div
|
|
key={event.id}
|
|
className="border rounded-lg p-3 text-sm shadow-sm hover:shadow-md transition-shadow"
|
|
>
|
|
{/* Event Title */}
|
|
<p className="font-semibold truncate">
|
|
{event.title || `Event ${event.id}`}
|
|
</p>
|
|
|
|
{/* Event Start Date/Time */}
|
|
{event.start && (
|
|
<div className="flex items-center gap-1 text-gray-500 dark:text-gray-400 text-xs">
|
|
<Clock className="h-3 w-3" />
|
|
<span>
|
|
{new Date(event.start).toLocaleString(
|
|
"fr-FR",
|
|
{
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
},
|
|
)}
|
|
</span>
|
|
{event.end && (
|
|
<>
|
|
<span>—</span>
|
|
<span>
|
|
{new Date(
|
|
event.end,
|
|
).toLocaleString("fr-FR", {
|
|
month: "short",
|
|
day: "numeric",
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
})}
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Full-day Badge */}
|
|
{event.fullday && (
|
|
<span className="inline-block mt-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full px-2 py-0.5">
|
|
Full Day
|
|
</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex justify-between">
|
|
<LocationDialog
|
|
location={location}
|
|
onUpdate={canUpdate ? onUpdate : undefined}
|
|
onDelete={canDelete ? onDelete : undefined}
|
|
/>
|
|
|
|
{canDelete && (
|
|
<Dialog
|
|
open={isDialogOpen}
|
|
onOpenChange={setIsDialogOpen}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button
|
|
variant="destructive"
|
|
className="flex items-center gap-2"
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
Supprimer
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Confirmation de suppression
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Cela supprimera définitivement cette
|
|
adresse:{" "}
|
|
<span className="font-semibold">
|
|
{location.street}
|
|
</span>
|
|
, {location.city}, {location.postalCode}
|
|
?
|
|
<br />
|
|
Êtes-vous sûr de vouloir continuer ?
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setIsDialogOpen(false)}
|
|
>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={handleDelete}
|
|
>
|
|
Supprimer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|