categories management
This commit is contained in:
129
pages/admin/categories.tsx
Normal file
129
pages/admin/categories.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import type { NextPage } from 'next'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
description: string
|
||||||
|
color: string
|
||||||
|
priority: number
|
||||||
|
is_active: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CategoriesManagement: NextPage = () => {
|
||||||
|
const [categories, setCategories] = useState<Category[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchCategories()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/admin/categories')
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
setCategories(data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch categories:', error)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleCategory = async (id: number, isActive: boolean) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/admin/categories/${id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ is_active: !isActive })
|
||||||
|
})
|
||||||
|
if (response.ok) {
|
||||||
|
fetchCategories()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update category:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Head>
|
||||||
|
<title>Správa kategórií - Infohliadka</title>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
|
||||||
|
<h1>Správa kategórií</h1>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div>Loading...</div>
|
||||||
|
) : (
|
||||||
|
<table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '20px' }}>
|
||||||
|
<thead>
|
||||||
|
<tr style={{ backgroundColor: '#f3f4f6' }}>
|
||||||
|
<th style={{ padding: '12px', textAlign: 'left', border: '1px solid #d1d5db' }}>Názov</th>
|
||||||
|
<th style={{ padding: '12px', textAlign: 'left', border: '1px solid #d1d5db' }}>Priorita</th>
|
||||||
|
<th style={{ padding: '12px', textAlign: 'left', border: '1px solid #d1d5db' }}>Farba</th>
|
||||||
|
<th style={{ padding: '12px', textAlign: 'left', border: '1px solid #d1d5db' }}>Aktívna</th>
|
||||||
|
<th style={{ padding: '12px', textAlign: 'left', border: '1px solid #d1d5db' }}>Akcie</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<tr key={category.id}>
|
||||||
|
<td style={{ padding: '12px', border: '1px solid #d1d5db' }}>
|
||||||
|
<strong>{category.name}</strong>
|
||||||
|
<div style={{ fontSize: '12px', color: '#6b7280' }}>
|
||||||
|
{category.description}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '12px', border: '1px solid #d1d5db' }}>
|
||||||
|
{category.priority}
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '12px', border: '1px solid #d1d5db' }}>
|
||||||
|
<div style={{
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
backgroundColor: category.color,
|
||||||
|
borderRadius: '4px',
|
||||||
|
display: 'inline-block'
|
||||||
|
}}></div>
|
||||||
|
<span style={{ marginLeft: '8px' }}>{category.color}</span>
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '12px', border: '1px solid #d1d5db' }}>
|
||||||
|
{category.is_active ? '✅' : '❌'}
|
||||||
|
</td>
|
||||||
|
<td style={{ padding: '12px', border: '1px solid #d1d5db' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => toggleCategory(category.id, category.is_active)}
|
||||||
|
style={{
|
||||||
|
padding: '4px 8px',
|
||||||
|
backgroundColor: category.is_active ? '#ef4444' : '#10b981',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{category.is_active ? 'Deaktivovať' : 'Aktivovať'}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginTop: '30px' }}>
|
||||||
|
<Link href="/admin">← Späť na dashboard</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoriesManagement
|
||||||
39
pages/api/admin/categories/[id].ts
Normal file
39
pages/api/admin/categories/[id].ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import sqlite3 from 'sqlite3'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
if (req.method !== 'PATCH') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.query
|
||||||
|
const { is_active } = req.body
|
||||||
|
|
||||||
|
const dbPath = path.join(process.cwd(), 'database', 'antihoax.db')
|
||||||
|
const db = new sqlite3.Database(dbPath)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
db.run(
|
||||||
|
'UPDATE categories SET is_active = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||||
|
[is_active, id],
|
||||||
|
function(err) {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json({ success: true })
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Database error:', error)
|
||||||
|
return res.status(500).json({ error: 'Internal server error' })
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
35
pages/api/admin/categories/index.ts
Normal file
35
pages/api/admin/categories/index.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import sqlite3 from 'sqlite3'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbPath = path.join(process.cwd(), 'database', 'antihoax.db')
|
||||||
|
const db = new sqlite3.Database(dbPath)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const categories = await new Promise<any[]>((resolve, reject) => {
|
||||||
|
db.all(
|
||||||
|
'SELECT * FROM categories ORDER BY priority DESC, name ASC',
|
||||||
|
(err, rows) => {
|
||||||
|
if (err) reject(err)
|
||||||
|
else resolve(rows)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json(categories)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Database error:', error)
|
||||||
|
return res.status(500).json({ error: 'Internal server error' })
|
||||||
|
} finally {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user