250 lines
13 KiB
TypeScript
250 lines
13 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import axios from "axios";
|
|
import AppLayout from '@/Layouts/AppLayout';
|
|
import { Video, Channel } from "@/types";
|
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
|
import {faDownload, faFileVideo, faFont} from "@fortawesome/free-solid-svg-icons";
|
|
import { router } from '@inertiajs/core';
|
|
import { Link, Head } from '@inertiajs/react';
|
|
import classNames from 'classnames';
|
|
import useRoute from '@/Hooks/useRoute';
|
|
import {MultiSelect} from "@/Components/MultiSelect";
|
|
import { format } from 'date-fns';
|
|
export default function Videos() {
|
|
const route = useRoute();
|
|
const [videos, setVideos] = useState<Video[]>([]);
|
|
const [channels, setChannels] = useState<Channel[]>([]);
|
|
const [distinctLanguages, setDistinctLanguages] = useState<string[]>([]);
|
|
const [loadingChannels, setLoadingChannels] = useState<boolean>(true);
|
|
const [loadingVideos, setLoadingVideos] = useState<boolean>(true);
|
|
|
|
// Filter states
|
|
const [selectedChannelIds, setSelectedChannelIds] = useState<string[]>([]);
|
|
const [selectedLanguages, setSelectedLanguages] = useState<string[]>([]);
|
|
const [startDate, setStartDate] = useState<string>('');
|
|
const [endDate, setEndDate] = useState<string>('');
|
|
|
|
|
|
// (1) Initial fetch on mount to get all channels and compute distinct languages (runs only once)
|
|
useEffect(() => {
|
|
setLoadingChannels(true);
|
|
axios.get('/api/channels/get')
|
|
.then(response => {
|
|
const channelsData = response.data as Channel[];
|
|
setChannels(channelsData);
|
|
// Compute distinct languages once (from the full unfiltered list)
|
|
const langs = [...new Set(channelsData.map(channel => channel.language))];
|
|
setDistinctLanguages(langs);
|
|
setLoadingChannels(false);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching channels:', error);
|
|
setLoadingChannels(false);
|
|
});
|
|
}, []);
|
|
|
|
// (2) Fetch channels based on language selection changes
|
|
useEffect(() => {
|
|
setLoadingChannels(true);
|
|
const params: Record<string, string | string[]> = {};
|
|
if (selectedLanguages.length > 0) {
|
|
params.languages = selectedLanguages.filter(lang => lang !== '');
|
|
}
|
|
axios.get('/api/channels/get', { params })
|
|
.then(response => {
|
|
setChannels(response.data);
|
|
setLoadingChannels(false);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching channels:', error);
|
|
setLoadingChannels(false);
|
|
});
|
|
}, [selectedLanguages]);
|
|
|
|
// Fetch clips when filters change (including videoId)
|
|
useEffect(() => {
|
|
setLoadingVideos(true);
|
|
const params: Record<string, string | string[]> = {};
|
|
|
|
if (selectedChannelIds.length > 0) {
|
|
params.channel_ids = selectedChannelIds.filter(id => id !== '');
|
|
}
|
|
if (selectedLanguages.length > 0) {
|
|
params.languages = selectedLanguages.filter(lang => lang !== '');
|
|
}
|
|
if (startDate) params.start_date = startDate;
|
|
if (endDate) params.end_date = endDate;
|
|
|
|
axios.get('/api/videos/get', { params })
|
|
.then(response => {
|
|
setVideos(response.data);
|
|
setLoadingVideos(false);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching clips:', error);
|
|
setLoadingVideos(false);
|
|
});
|
|
}, [selectedChannelIds, selectedLanguages, startDate, endDate]);
|
|
|
|
return (
|
|
<AppLayout
|
|
title="Videos"
|
|
renderHeader={() => (
|
|
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
|
Videos
|
|
</h2>
|
|
)}
|
|
>
|
|
<div className="py-12">
|
|
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
<div className="bg-white dark:bg-gray-800 overflow-hidden shadow-xl sm:rounded-lg p-6">
|
|
{/* Filter Options */}
|
|
<div className="mb-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
|
{/* Channel Filter */}
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Channel</span>
|
|
</label>
|
|
{loadingChannels ? (
|
|
<span className="loading loading-dots loading-md"></span>
|
|
) : (
|
|
<MultiSelect
|
|
label="Channels"
|
|
options={channels.map(ch => ({
|
|
value: ch.id.toString(),
|
|
label: ch.channel_name,
|
|
}))}
|
|
selected={selectedChannelIds}
|
|
onChange={setSelectedChannelIds}
|
|
/>
|
|
)}
|
|
</div>
|
|
{/* Language Filter */}
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Language</span>
|
|
</label>
|
|
{loadingChannels ? (
|
|
<span className="loading loading-dots loading-md"></span>
|
|
) : (
|
|
<MultiSelect
|
|
label="Languages"
|
|
options={distinctLanguages.map(ln => ({
|
|
value: ln,
|
|
label: ln,
|
|
}))}
|
|
selected={selectedLanguages}
|
|
onChange={setSelectedLanguages}
|
|
/>
|
|
)}
|
|
</div>
|
|
{/* Start Date Filter */}
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Start Date</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
className="input input-bordered"
|
|
value={startDate}
|
|
onChange={(e) => setStartDate(e.target.value)}
|
|
/>
|
|
</div>
|
|
{/* End Date Filter */}
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">End Date</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
className="input input-bordered"
|
|
value={endDate}
|
|
onChange={(e) => setEndDate(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="table">
|
|
{/* head */}
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<label>
|
|
<input type="checkbox" className="checkbox"/>
|
|
</label>
|
|
</th>
|
|
<th>Channel</th>
|
|
<th>Title</th>
|
|
<th></th>
|
|
<th>...</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{loadingVideos ? (
|
|
<div className="flex justify-center items-center">
|
|
<span className="loading loading-dots loading-lg"></span>
|
|
</div>
|
|
) :
|
|
videos.map((video) => (
|
|
<tr>
|
|
<th>
|
|
<label>
|
|
<input type="checkbox" className="checkbox"/>
|
|
</label>
|
|
</th>
|
|
<td className="pr-0">
|
|
<div className="flex items-center gap-3">
|
|
<div className="avatar">
|
|
<div className="mask mask-squircle h-12 w-12">
|
|
<img
|
|
src={video.channel.img_url}
|
|
alt={video.channel.channel_name}/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="font-bold">{video.channel.channel_name}</div>
|
|
<div className="text-sm opacity-50">{video.external_date ? format(video.external_date, 'dd-MM-yy') : ''}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div className="flex flex-col w-max max-w-[500px]">
|
|
<div>
|
|
{video.name}
|
|
</div>
|
|
<a href={route('clips', {video_id: video.id})} target="_blank">
|
|
<span className="badge badge-ghost badge-sm">
|
|
Clip count: {video.clips.length || 0}
|
|
</span>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<FontAwesomeIcon icon={faDownload} className={`${video.data_downloaded ? "text-green-500" : video.data_downloading ? "text-yellow-500" : "text-gray-500"}`} />
|
|
<FontAwesomeIcon icon={faFileVideo} className={`${video.processed ? "text-green-500" : "text-gray-500"}`} />
|
|
</div>
|
|
</td>
|
|
<th>
|
|
<button className="btn btn-ghost btn-xs opacity-20 pointer-events-none cursor-not-allowed">Actions</button>
|
|
</th>
|
|
</tr>
|
|
))
|
|
}
|
|
|
|
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
);
|
|
}
|