Add search to videos
This commit is contained in:
parent
7052fc0077
commit
ad83f0643c
|
|
@ -126,6 +126,20 @@ export const videoOperations = {
|
||||||
.all(status) as Video[];
|
.all(status) as Video[];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
search: (query: string, status?: string) => {
|
||||||
|
const searchPattern = `%${query}%`;
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
return db.prepare(
|
||||||
|
'SELECT * FROM videos WHERE title LIKE ? COLLATE NOCASE AND status = ? ORDER BY addedAt DESC'
|
||||||
|
).all(searchPattern, status) as Video[];
|
||||||
|
} else {
|
||||||
|
return db.prepare(
|
||||||
|
'SELECT * FROM videos WHERE title LIKE ? COLLATE NOCASE ORDER BY addedAt DESC'
|
||||||
|
).all(searchPattern) as Video[];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getNextPending: () => {
|
getNextPending: () => {
|
||||||
return db.prepare('SELECT * FROM videos WHERE status = ? ORDER BY addedAt ASC LIMIT 1')
|
return db.prepare('SELECT * FROM videos WHERE status = ? ORDER BY addedAt ASC LIMIT 1')
|
||||||
.get('pending') as Video | undefined;
|
.get('pending') as Video | undefined;
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,17 @@ const router = Router();
|
||||||
// Get all videos
|
// Get all videos
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { status } = req.query;
|
const { status, search } = req.query;
|
||||||
|
|
||||||
let videos;
|
let videos;
|
||||||
if (status && typeof status === 'string') {
|
|
||||||
|
// If search query is provided, use search
|
||||||
|
if (search && typeof search === 'string') {
|
||||||
|
const statusFilter = status && typeof status === 'string' ? status : undefined;
|
||||||
|
videos = videoOperations.search(search, statusFilter);
|
||||||
|
}
|
||||||
|
// Otherwise use status filter or get all
|
||||||
|
else if (status && typeof status === 'string') {
|
||||||
videos = videoOperations.getByStatus(status);
|
videos = videoOperations.getByStatus(status);
|
||||||
} else {
|
} else {
|
||||||
videos = videoOperations.getAll();
|
videos = videoOperations.getAll();
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,18 @@ function VideosPage() {
|
||||||
const [addUrl, setAddUrl] = useState('');
|
const [addUrl, setAddUrl] = useState('');
|
||||||
const [adding, setAdding] = useState(false);
|
const [adding, setAdding] = useState(false);
|
||||||
const [filter, setFilter] = useState<string>('all');
|
const [filter, setFilter] = useState<string>('all');
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadVideos();
|
loadVideos();
|
||||||
}, [filter]);
|
}, [filter, searchQuery]);
|
||||||
|
|
||||||
const loadVideos = async () => {
|
const loadVideos = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = filter === 'all' ? await videosAPI.getAll() : await videosAPI.getAll(filter);
|
const statusFilter = filter === 'all' ? undefined : filter;
|
||||||
|
const search = searchQuery.trim() || undefined;
|
||||||
|
const data = await videosAPI.getAll(statusFilter, search);
|
||||||
setVideos(data);
|
setVideos(data);
|
||||||
setError('');
|
setError('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -127,47 +130,82 @@ function VideosPage() {
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex gap-2">
|
<div className="mt-6 space-y-4">
|
||||||
<button
|
<div className="flex gap-2">
|
||||||
onClick={() => setFilter('all')}
|
<button
|
||||||
className={`px-4 py-2 rounded ${
|
onClick={() => setFilter('all')}
|
||||||
filter === 'all' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
className={`px-4 py-2 rounded ${
|
||||||
}`}
|
filter === 'all' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||||
>
|
}`}
|
||||||
All
|
>
|
||||||
</button>
|
All
|
||||||
<button
|
</button>
|
||||||
onClick={() => setFilter('completed')}
|
<button
|
||||||
className={`px-4 py-2 rounded ${
|
onClick={() => setFilter('completed')}
|
||||||
filter === 'completed' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
className={`px-4 py-2 rounded ${
|
||||||
}`}
|
filter === 'completed' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||||
>
|
}`}
|
||||||
Completed
|
>
|
||||||
</button>
|
Completed
|
||||||
<button
|
</button>
|
||||||
onClick={() => setFilter('pending')}
|
<button
|
||||||
className={`px-4 py-2 rounded ${
|
onClick={() => setFilter('pending')}
|
||||||
filter === 'pending' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
className={`px-4 py-2 rounded ${
|
||||||
}`}
|
filter === 'pending' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||||
>
|
}`}
|
||||||
Pending
|
>
|
||||||
</button>
|
Pending
|
||||||
<button
|
</button>
|
||||||
onClick={() => setFilter('downloading')}
|
<button
|
||||||
className={`px-4 py-2 rounded ${
|
onClick={() => setFilter('downloading')}
|
||||||
filter === 'downloading' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
className={`px-4 py-2 rounded ${
|
||||||
}`}
|
filter === 'downloading' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||||
>
|
}`}
|
||||||
Downloading
|
>
|
||||||
</button>
|
Downloading
|
||||||
<button
|
</button>
|
||||||
onClick={() => setFilter('failed')}
|
<button
|
||||||
className={`px-4 py-2 rounded ${
|
onClick={() => setFilter('failed')}
|
||||||
filter === 'failed' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
className={`px-4 py-2 rounded ${
|
||||||
}`}
|
filter === 'failed' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||||
>
|
}`}
|
||||||
Failed
|
>
|
||||||
</button>
|
Failed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<div className="flex-1 relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Search videos by title..."
|
||||||
|
className="w-full rounded-md border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 pl-10"
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
className="px-4 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-md">
|
<div className="mt-6 bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-md">
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,14 @@ export const channelsAPI = {
|
||||||
|
|
||||||
// Videos API
|
// Videos API
|
||||||
export const videosAPI = {
|
export const videosAPI = {
|
||||||
getAll: (status?: string) => {
|
getAll: (status?: string, search?: string) => {
|
||||||
const url = status
|
const params = new URLSearchParams();
|
||||||
? `${API_BASE}/videos?status=${status}`
|
if (status) params.append('status', status);
|
||||||
|
if (search) params.append('search', search);
|
||||||
|
|
||||||
|
const queryString = params.toString();
|
||||||
|
const url = queryString
|
||||||
|
? `${API_BASE}/videos?${queryString}`
|
||||||
: `${API_BASE}/videos`;
|
: `${API_BASE}/videos`;
|
||||||
return fetchJSON<Video[]>(url);
|
return fetchJSON<Video[]>(url);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue