feat: add Pagination component for video list navigation
- Implemented a reusable Pagination component with first/last, previous/next, and numbered page buttons. - Added PageInfo component to display current page and total items. - Integrated pagination into VideoList component, allowing users to navigate through video pages. - Updated useVideoList hook to manage current page and total pages state. - Modified videoApi service to support pagination with offset-based API. - Enhanced VideoCard styling for better UI consistency. - Updated Tailwind CSS configuration to include custom colors and shadows for branding. - Refactored video file settings to use 'h264' codec for better compatibility.
This commit is contained in:
@@ -11,7 +11,7 @@ interface ScheduleModalProps {
|
||||
export function ScheduleModal({ experiment, onClose, onScheduleUpdated }: ScheduleModalProps) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
|
||||
// Initialize with existing scheduled date or current date/time
|
||||
const getInitialDateTime = () => {
|
||||
if (experiment.scheduled_date) {
|
||||
@@ -21,7 +21,7 @@ export function ScheduleModal({ experiment, onClose, onScheduleUpdated }: Schedu
|
||||
time: date.toTimeString().slice(0, 5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const now = new Date()
|
||||
// Set to next hour by default
|
||||
now.setHours(now.getHours() + 1, 0, 0, 0)
|
||||
@@ -92,21 +92,38 @@ export function ScheduleModal({ experiment, onClose, onScheduleUpdated }: Schedu
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4">
|
||||
<div className="relative bg-white rounded-xl shadow-2xl w-full max-w-md mx-auto">
|
||||
<div className="fixed inset-0 flex items-center justify-center overflow-y-auto modal z-999999">
|
||||
<div
|
||||
className="fixed inset-0 h-full w-full bg-gray-400/50 backdrop-blur-[32px]"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className="relative w-full rounded-2xl bg-white shadow-theme-xl dark:bg-gray-900 max-w-md mx-auto p-4" onClick={(e) => e.stopPropagation()}>
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-3 top-3 z-999 flex h-9.5 w-9.5 items-center justify-center rounded-full bg-gray-100 text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white sm:right-6 sm:top-6 sm:h-11 sm:w-11"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.04289 16.5413C5.65237 16.9318 5.65237 17.565 6.04289 17.9555C6.43342 18.346 7.06658 18.346 7.45711 17.9555L11.9987 13.4139L16.5408 17.956C16.9313 18.3466 17.5645 18.3466 17.955 17.956C18.3455 17.5655 18.3455 16.9323 17.955 16.5418L13.4129 11.9997L17.955 7.4576C18.3455 7.06707 18.3455 6.43391 17.955 6.04338C17.5645 5.65286 16.9313 5.65286 16.5408 6.04338L11.9987 10.5855L7.45711 6.0439C7.06658 5.65338 6.43342 5.65338 6.04289 6.0439C5.65237 6.43442 5.65237 7.06759 6.04289 7.45811L10.5845 11.9997L6.04289 16.5413Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-800">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-white/90">
|
||||
{isScheduled ? 'Update Schedule' : 'Schedule Experiment'}
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg p-2 transition-colors"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
@@ -138,31 +155,45 @@ export function ScheduleModal({ experiment, onClose, onScheduleUpdated }: Schedu
|
||||
{/* Schedule Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="date" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label htmlFor="date" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Date *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
value={dateTime.date}
|
||||
onChange={(e) => setDateTime({ ...dateTime, date: e.target.value })}
|
||||
className="max-w-xs px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm"
|
||||
required
|
||||
/>
|
||||
<div className="relative max-w-xs">
|
||||
<input
|
||||
type="date"
|
||||
id="date"
|
||||
value={dateTime.date}
|
||||
onChange={(e) => setDateTime({ ...dateTime, date: e.target.value })}
|
||||
className="h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
<svg className="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="time" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label htmlFor="time" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Time *
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
id="time"
|
||||
value={dateTime.time}
|
||||
onChange={(e) => setDateTime({ ...dateTime, time: e.target.value })}
|
||||
className="max-w-xs px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-sm"
|
||||
required
|
||||
/>
|
||||
<div className="relative max-w-xs">
|
||||
<input
|
||||
type="time"
|
||||
id="time"
|
||||
value={dateTime.time}
|
||||
onChange={(e) => setDateTime({ ...dateTime, time: e.target.value })}
|
||||
className="h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
<svg className="size-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
@@ -173,26 +204,26 @@ export function ScheduleModal({ experiment, onClose, onScheduleUpdated }: Schedu
|
||||
type="button"
|
||||
onClick={handleRemoveSchedule}
|
||||
disabled={loading}
|
||||
className="px-4 py-2 text-sm font-medium text-red-600 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors disabled:opacity-50"
|
||||
className="px-4 py-2 text-sm font-medium text-error-600 hover:text-error-700 hover:bg-error-50 dark:text-error-500 dark:hover:bg-error-500/15 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
Remove Schedule
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
disabled={loading}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors disabled:opacity-50"
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors disabled:opacity-50"
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-brand-500 hover:bg-brand-600 focus:outline-none focus:ring-3 focus:ring-brand-500/10 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Saving...' : (isScheduled ? 'Update Schedule' : 'Schedule Experiment')}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user