This repository has been archived on 2026-04-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
KLC-Admin-Panel-Frontend-Fi…/src/components/pages/Facilities360.tsx
2025-10-29 19:21:35 +05:30

439 lines
16 KiB
TypeScript

import React, { useState } from 'react';
import { AuthenticatedLayout } from '../layout/AuthenticatedLayout';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Input } from '../ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../ui/table';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
import { Checkbox } from '../ui/checkbox';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '../ui/alert-dialog';
import { toast } from "sonner";
import {
Plus,
Download,
MoreHorizontal,
Eye,
ExternalLink,
Trash2,
Upload,
Camera,
MapPin,
Calendar,
Activity,
Filter
} from 'lucide-react';
import { Route } from '../../types/routes';
interface Facilities360Props {
onNavigate: (route: Route) => void;
onLogout: () => void;
user: any;
}
// Mock data for 360 tours
const mock360Tours = [
{
id: 'tour-1',
title: 'Main Campus Building',
photoCount: 24,
sequenceCount: 2,
lastUpdated: '2024-01-15',
publishStatus: 'Published',
views: 1247,
place: 'Kautilya Leadership Centre, New Delhi',
description: 'Complete tour of the main academic building including classrooms, library, and common areas'
},
{
id: 'tour-2',
title: 'Executive Training Center',
photoCount: 18,
sequenceCount: 1,
lastUpdated: '2024-01-12',
publishStatus: 'Pending',
views: 0,
place: 'KLC Executive Center, Gurugram',
description: 'Executive training facilities and boardrooms'
},
{
id: 'tour-3',
title: 'Library & Study Areas',
photoCount: 15,
sequenceCount: 0,
lastUpdated: '2024-01-10',
publishStatus: 'Rejected',
views: 45,
place: 'Kautilya Leadership Centre, New Delhi',
description: 'Digital library, reading rooms, and collaborative study spaces'
},
{
id: 'tour-4',
title: 'Innovation Lab',
photoCount: 12,
sequenceCount: 1,
lastUpdated: '2024-01-08',
publishStatus: 'Published',
views: 892,
place: 'KLC Innovation Hub, Bangalore',
description: 'Technology lab with latest equipment and collaboration spaces'
}
];
export function Facilities360({ onNavigate, onLogout, user }: Facilities360Props) {
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [selectedTours, setSelectedTours] = useState<string[]>([]);
const [showBulkActions, setShowBulkActions] = useState(false);
const breadcrumbs = [
{ label: 'Facilities 360 Tour' }
];
const filteredTours = mock360Tours.filter(tour => {
const matchesSearch = tour.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
tour.place.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || tour.publishStatus.toLowerCase() === statusFilter;
return matchesSearch && matchesStatus;
});
const getStatusVariant = (status: string) => {
switch (status.toLowerCase()) {
case 'published': return 'default';
case 'pending': return 'secondary';
case 'rejected': return 'destructive';
default: return 'outline';
}
};
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'published': return 'text-green-600';
case 'pending': return 'text-yellow-600';
case 'rejected': return 'text-red-600';
default: return 'text-gray-600';
}
};
const handleSelectTour = (tourId: string, checked: boolean) => {
if (checked) {
setSelectedTours(prev => [...prev, tourId]);
} else {
setSelectedTours(prev => prev.filter(id => id !== tourId));
}
};
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedTours(filteredTours.map(tour => tour.id));
} else {
setSelectedTours([]);
}
};
const handleBulkPublish = () => {
toast.success(`Publishing ${selectedTours.length} tours to Google Maps`, {
duration: 3000
});
setSelectedTours([]);
setShowBulkActions(false);
};
const handleBulkUnpublish = () => {
toast.success(`Unpublishing ${selectedTours.length} tours from Google Maps`, {
duration: 3000
});
setSelectedTours([]);
setShowBulkActions(false);
};
const handleBulkDelete = () => {
toast.success(`Deleted ${selectedTours.length} tours`, {
duration: 3000
});
setSelectedTours([]);
setShowBulkActions(false);
};
const handleImportFromStreetView = () => {
toast.info('Import from Street View feature coming soon', {
duration: 2000
});
};
const renderEmptyState = () => (
<div className="text-center py-12">
<div className="mx-auto w-24 h-24 bg-muted rounded-full flex items-center justify-center mb-4">
<Camera className="h-12 w-12 text-muted-foreground" />
</div>
<h3 className="text-lg font-medium mb-2">No tours yet</h3>
<p className="text-muted-foreground mb-6">Create your first 360° facility tour to get started</p>
<Button
onClick={() => onNavigate('/facilities-360/new')}
className="min-h-[44px]"
style={{ backgroundColor: "var(--color-brand-primary)" }}
>
<Plus className="h-4 w-4 mr-2" />
New Tour
</Button>
</div>
);
return (
<AuthenticatedLayout
currentRoute="/facilities-360"
onNavigate={onNavigate}
onLogout={onLogout}
user={user}
breadcrumbs={breadcrumbs}
>
<div className="p-6 space-y-6 max-w-[1440px] mx-auto">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1>Facilities 360 Tour</h1>
<p className="text-muted-foreground mt-1">
Create, manage, and publish immersive 360° facility tours
</p>
</div>
<div className="flex items-center gap-3">
<Button
variant="outline"
onClick={handleImportFromStreetView}
className="min-h-[44px]"
>
<Download className="h-4 w-4 mr-2" />
Import from Street View
</Button>
<Button
onClick={() => onNavigate('/facilities-360/new')}
className="min-h-[44px]"
style={{ backgroundColor: "var(--color-brand-primary)" }}
>
<Plus className="h-4 w-4 mr-2" />
New Tour
</Button>
</div>
</div>
{filteredTours.length === 0 && searchTerm === '' && statusFilter === 'all' ? (
renderEmptyState()
) : (
<Card>
<CardHeader>
<CardTitle>Tours Library</CardTitle>
<div className="flex items-center gap-4 pt-4">
<div className="flex-1">
<Input
placeholder="Search tours or places..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="max-w-sm min-h-[44px]"
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-[140px] min-h-[44px]">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="published">Published</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="rejected">Rejected</SelectItem>
</SelectContent>
</Select>
{selectedTours.length > 0 && (
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={handleBulkPublish}
className="min-h-[44px]"
>
Publish ({selectedTours.length})
</Button>
<Button
variant="outline"
size="sm"
onClick={handleBulkUnpublish}
className="min-h-[44px]"
>
Unpublish
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
size="sm"
className="min-h-[44px]"
>
<Trash2 className="h-4 w-4 mr-1" />
Delete
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Selected Tours</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete {selectedTours.length} selected tours?
This action cannot be undone and will remove the tours from Google Maps.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleBulkDelete}>
Delete Tours
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
)}
</div>
</CardHeader>
<CardContent>
{filteredTours.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
No tours found matching your criteria.
</div>
) : (
<div className="border rounded-lg overflow-hidden">
<Table>
<TableHeader className="bg-muted/50">
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedTours.length === filteredTours.length}
onCheckedChange={handleSelectAll}
aria-label="Select all tours"
/>
</TableHead>
<TableHead className="min-w-[250px]">Tour Title</TableHead>
<TableHead>Photos Sequences</TableHead>
<TableHead>Last Updated</TableHead>
<TableHead>Publish Status</TableHead>
<TableHead>Views</TableHead>
<TableHead className="w-12">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredTours.map((tour) => (
<TableRow key={tour.id}>
<TableCell>
<Checkbox
checked={selectedTours.includes(tour.id)}
onCheckedChange={(checked) => handleSelectTour(tour.id, checked as boolean)}
aria-label={`Select ${tour.title}`}
/>
</TableCell>
<TableCell>
<div>
<div className="font-medium">{tour.title}</div>
<div className="text-sm text-muted-foreground flex items-center gap-1 mt-1">
<MapPin className="h-3 w-3" />
{tour.place}
</div>
<div className="text-xs text-muted-foreground mt-1">
{tour.description}
</div>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-4 text-sm">
<div className="flex items-center gap-1">
<Camera className="h-4 w-4 text-muted-foreground" />
{tour.photoCount}
</div>
<div className="flex items-center gap-1">
<Activity className="h-4 w-4 text-muted-foreground" />
{tour.sequenceCount}
</div>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-1 text-sm">
<Calendar className="h-4 w-4 text-muted-foreground" />
{tour.lastUpdated}
</div>
</TableCell>
<TableCell>
<Badge variant={getStatusVariant(tour.publishStatus)}>
{tour.publishStatus}
</Badge>
</TableCell>
<TableCell>
<div className="text-sm font-medium">
{tour.views.toLocaleString()}
</div>
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => onNavigate(`/facilities-360/${tour.id}`)}
>
<Eye className="h-4 w-4 mr-2" />
Open
</DropdownMenuItem>
<DropdownMenuItem>
<ExternalLink className="h-4 w-4 mr-2" />
Preview
</DropdownMenuItem>
<DropdownMenuItem>
<Upload className="h-4 w-4 mr-2" />
{tour.publishStatus === 'Published' ? 'Unpublish' : 'Publish'}
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
<Trash2 className="h-4 w-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</CardContent>
</Card>
)}
</div>
</AuthenticatedLayout>
);
}