erp-platform/ui/src/components/intranet/Reservations/index.tsx
2025-10-19 10:39:10 +03:00

313 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { HiKey, HiCalendar, HiTruck, HiCog, HiPlus, HiXMark } from 'react-icons/hi2'
import dayjs from 'dayjs'
import { mockReservations, Reservation } from '../../../mocks/mockIntranetData'
const ReservationsModule: React.FC = () => {
const [selectedType, setSelectedType] = useState<'all' | 'room' | 'vehicle' | 'equipment'>('all')
const [showNewReservation, setShowNewReservation] = useState(false)
const filteredReservations =
selectedType === 'all'
? mockReservations
: mockReservations.filter((r) => r.type === selectedType)
const getTypeIcon = (type: string) => {
switch (type) {
case 'room':
return <HiKey className="w-5 h-5" />
case 'vehicle':
return <HiTruck className="w-5 h-5" />
case 'equipment':
return <HiCog className="w-5 h-5" />
default:
return <HiKey className="w-5 h-5" />
}
}
const getTypeLabel = (type: string) => {
const labels: Record<string, string> = {
room: 'Toplantı Salonu',
vehicle: 'Araç',
equipment: 'Ekipman',
}
return labels[type] || type
}
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
pending: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300',
approved: 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300',
rejected: 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300',
completed: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300',
}
return colors[status] || colors.pending
}
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
pending: 'Bekliyor',
approved: 'Onaylandı',
rejected: 'Reddedildi',
completed: 'Tamamlandı',
}
return labels[status] || status
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 p-6">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">🔑 Rezervasyonlar</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Oda, araç ve ekipman rezervasyonları
</p>
</div>
<button
onClick={() => setShowNewReservation(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<HiPlus className="w-5 h-5" />
Yeni Rezervasyon
</button>
</div>
{/* Type Filter */}
<div className="flex gap-3">
{[
{ value: 'all' as const, label: 'Tümü', icon: HiCalendar },
{ value: 'room' as const, label: 'Toplantı Salonu', icon: HiKey },
{ value: 'vehicle' as const, label: 'Araç', icon: HiTruck },
{ value: 'equipment' as const, label: 'Ekipman', icon: HiCog },
].map((type) => (
<button
key={type.value}
onClick={() => setSelectedType(type.value)}
className={`flex items-center gap-2 px-4 py-2 rounded-lg border-2 transition-all ${
selectedType === type.value
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
}`}
>
<type.icon className="w-5 h-5" />
{type.label}
</button>
))}
</div>
{/* Reservations List */}
<div className="space-y-4">
{filteredReservations.map((reservation: Reservation, idx: number) => (
<motion.div
key={reservation.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.05 }}
className="bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all"
>
<div className="flex items-start gap-4">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center text-blue-600 dark:text-blue-400">
{getTypeIcon(reservation.type)}
</div>
<div className="flex-1">
<div className="flex items-start justify-between mb-2">
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{reservation.resourceName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{getTypeLabel(reservation.type)}
</p>
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(reservation.status)}`}
>
{getStatusLabel(reservation.status)}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-3">
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Başlangıç</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(reservation.startDate).format('DD MMM, HH:mm')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Bitiş</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{dayjs(reservation.endDate).format('DD MMM, HH:mm')}
</p>
</div>
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Rezerve Eden</p>
<div className="flex items-center gap-2 mt-1">
<img
src={reservation.bookedBy.avatar}
alt={reservation.bookedBy.fullName}
className="w-5 h-5 rounded-full"
/>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{reservation.bookedBy.fullName}
</p>
</div>
</div>
{reservation.participants && (
<div>
<p className="text-xs text-gray-500 dark:text-gray-400">Katılımcı</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">
{reservation.participants} kişi
</p>
</div>
)}
</div>
<div className="mb-3">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
Amaç: {reservation.purpose}
</p>
</div>
{reservation.notes && (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
<p className="text-xs text-gray-600 dark:text-gray-400">
<strong>Not:</strong> {reservation.notes}
</p>
</div>
)}
</div>
</div>
</motion.div>
))}
{filteredReservations.length === 0 && (
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<HiCalendar className="w-16 h-16 mx-auto mb-4 opacity-20" />
<p>Rezervasyon bulunamadı</p>
</div>
)}
</div>
</div>
{/* New Reservation Modal */}
<AnimatePresence>
{showNewReservation && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/50 z-40"
onClick={() => setShowNewReservation(false)}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full"
>
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
Yeni Rezervasyon Oluştur
</h2>
<button
onClick={() => setShowNewReservation(false)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<HiXMark className="w-5 h-5 text-gray-500" />
</button>
</div>
<div className="p-6 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Rezervasyon Tipi
</label>
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Toplantı Salonu</option>
<option>Araç</option>
<option>Ekipman</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Kaynak Seçin
</label>
<select className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
<option>Toplantı Salonu A</option>
<option>Toplantı Salonu B</option>
<option>Eğitim Salonu</option>
</select>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Başlangıç Tarihi
</label>
<input
type="datetime-local"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Bitiş Tarihi
</label>
<input
type="datetime-local"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Amaç
</label>
<input
type="text"
placeholder="Rezervasyon amacını yazın"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Notlar (Opsiyonel)
</label>
<textarea
rows={3}
placeholder="Ek notlarınızı yazın"
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
/>
</div>
<div className="flex gap-3 pt-4">
<button
onClick={() => setShowNewReservation(false)}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
İptal
</button>
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
Rezervasyon Oluştur
</button>
</div>
</div>
</motion.div>
</div>
</>
)}
</AnimatePresence>
</div>
)
}
export default ReservationsModule