mirror of
https://github.com/skidoodle/erettsegi-browser.git
synced 2025-02-15 05:39:15 +01:00
minor changes pt.2
This commit is contained in:
parent
6275b083db
commit
76f75a2f60
17 changed files with 416 additions and 276 deletions
35
.eslintrc.cjs
Normal file
35
.eslintrc.cjs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/** @type {import("eslint").Linter.Config} */
|
||||||
|
const config = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@next/next/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended-type-checked',
|
||||||
|
'plugin:@typescript-eslint/stylistic-type-checked',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/array-type': 'off',
|
||||||
|
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||||
|
|
||||||
|
'@typescript-eslint/consistent-type-imports': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
prefer: 'type-imports',
|
||||||
|
fixStyle: 'inline-type-imports',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
||||||
|
'@typescript-eslint/require-await': 'off',
|
||||||
|
'@typescript-eslint/no-misused-promises': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
checksVoidReturn: { attributes: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
22
package.json
22
package.json
|
@ -11,20 +11,28 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nextui-org/react": "^2.2.9",
|
"@nextui-org/react": "^2.2.9",
|
||||||
"@types/node": "20.10.4",
|
|
||||||
"@types/react": "18.2.43",
|
|
||||||
"@types/react-dom": "18.2.17",
|
|
||||||
"@vercel/analytics": "^1.1.1",
|
"@vercel/analytics": "^1.1.1",
|
||||||
"autoprefixer": "10.4.16",
|
|
||||||
"eslint": "8.55.0",
|
|
||||||
"eslint-config-next": "14.0.4",
|
"eslint-config-next": "14.0.4",
|
||||||
"framer-motion": "^10.16.16",
|
"framer-motion": "^10.16.16",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"postcss": "8.4.32",
|
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@next/eslint-plugin-next": "^14.0.4",
|
||||||
|
"@types/eslint": "^8.44.8",
|
||||||
|
"@types/node": "20.10.4",
|
||||||
|
"@types/react": "18.2.43",
|
||||||
|
"@types/react-dom": "18.2.17",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||||
|
"@typescript-eslint/parser": "^6.14.0",
|
||||||
|
"autoprefixer": "10.4.16",
|
||||||
|
"eslint": "8.55.0",
|
||||||
|
"postcss": "8.4.32",
|
||||||
|
"prettier": "^3.1.1",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||||
"tailwindcss": "3.3.6",
|
"tailwindcss": "3.3.6",
|
||||||
"typescript": "5.3.3"
|
"typescript": "5.3.3"
|
||||||
}
|
}
|
||||||
|
|
407
pnpm-lock.yaml
generated
407
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
8
postcss.config.cjs
Normal file
8
postcss.config.cjs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
6
prettier.config.js
Normal file
6
prettier.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
|
||||||
|
const config = {
|
||||||
|
plugins: ['prettier-plugin-tailwindcss'],
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
|
@ -1,27 +1,68 @@
|
||||||
import { Button } from '@nextui-org/react'
|
import { Button } from '@nextui-org/react'
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import type { ButtonProps } from '@/utils/props'
|
||||||
|
import type { ButtonColor } from '@/utils/types'
|
||||||
|
|
||||||
export const PdfButton: React.FC<{ label: string; link: string }> = ({
|
const CustomButton: React.FC<ButtonProps> = ({ label, link }) => {
|
||||||
label,
|
const [status, setStatus] = useState<number>(0)
|
||||||
link,
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||||
}) => (
|
|
||||||
|
const checkLinkStatus = async (): Promise<void> => {
|
||||||
|
if (link) {
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/validate?link=${encodeURIComponent(link)}`
|
||||||
|
)
|
||||||
|
const data = (await response.json()) as { status: number }
|
||||||
|
setStatus(data.status)
|
||||||
|
} catch (error) {
|
||||||
|
setStatus(500)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void checkLinkStatus()
|
||||||
|
}, [link])
|
||||||
|
|
||||||
|
const getColor = (): ButtonColor => {
|
||||||
|
if (status === 200) {
|
||||||
|
return 'primary'
|
||||||
|
} else if (status === 404) {
|
||||||
|
return 'danger'
|
||||||
|
} else {
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (status === 200 && link) {
|
||||||
|
window.open(link)
|
||||||
|
} else {
|
||||||
|
console.error('A hivatkozás nem elérhető.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
isDisabled={!link}
|
isDisabled={status !== 200 || !link || isLoading}
|
||||||
className='w-24 mt-3 text-sm bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-2'
|
isLoading={isLoading}
|
||||||
onClick={link ? () => window.open(link) : () => {}}
|
className='w-24 mt-3 text-sm font-bold py-2 px-2'
|
||||||
|
color={getColor()}
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PdfButton: React.FC<ButtonProps> = ({ label, link }) => (
|
||||||
|
<CustomButton label={label} link={link} />
|
||||||
)
|
)
|
||||||
|
|
||||||
export const ZipButton: React.FC<{ label: string; link: string }> = ({
|
export const ZipButton: React.FC<ButtonProps> = ({ label, link }) => (
|
||||||
label,
|
<CustomButton label={label} link={link} />
|
||||||
link,
|
|
||||||
}) => (
|
|
||||||
<Button
|
|
||||||
isDisabled={!link}
|
|
||||||
className='w-24 mt-3 text-sm bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-2'
|
|
||||||
onClick={link ? () => window.open(link) : () => {}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Select, SelectItem } from '@nextui-org/react'
|
import { Select, SelectItem } from '@nextui-org/react'
|
||||||
import { SelectorProps } from '@/utils/props'
|
import type { SelectorProps } from '@/utils/props'
|
||||||
|
|
||||||
export const SubjectSelector: React.FC<
|
export const SubjectSelector: React.FC<
|
||||||
Pick<SelectorProps, 'selectedSubject' | 'setSelectedSubject' | 'subjects'>
|
Pick<SelectorProps, 'selectedSubject' | 'setSelectedSubject' | 'subjects'>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||||
import { Analytics } from '@vercel/analytics/react'
|
import { Analytics } from '@vercel/analytics/react'
|
||||||
import { NextUIProvider } from '@nextui-org/react'
|
import { NextUIProvider } from '@nextui-org/react'
|
||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
import { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import '@/styles/globals.css'
|
import '@/styles/globals.css'
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { subjects } from '@/utils/subjects'
|
import { subjects } from '@/utils/subjects'
|
||||||
|
|
||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const { vizsgatargy, ev, idoszak, szint } = req.query
|
const { vizsgatargy, ev, idoszak, szint } = req.query as {
|
||||||
|
vizsgatargy: string
|
||||||
|
ev: string
|
||||||
|
idoszak: string
|
||||||
|
szint: string
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrl = `https://dload-oktatas.educatio.hu/erettsegi/feladatok_${ev}${idoszak}_${szint}/`
|
const baseUrl = `https://dload-oktatas.educatio.hu/erettsegi/feladatok_${ev}${idoszak}_${szint}/`
|
||||||
|
|
||||||
const missingParams = []
|
const missingParams = []
|
||||||
|
@ -17,16 +23,16 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
.json({ error: `Hiányzó paraméterek: ${missingParams.join(', ')}` })
|
.json({ error: `Hiányzó paraméterek: ${missingParams.join(', ')}` })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev! <= '2012') {
|
if (ev <= '2012') {
|
||||||
return res.status(400).json({ error: 'Érvénytelen év' })
|
return res.status(400).json({ error: 'Érvénytelen év' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const validSubjects = subjects.map((subject) => subject.value)
|
const validSubjects = subjects.map((subject) => subject.value)
|
||||||
if (!vizsgatargy || !validSubjects.includes(vizsgatargy as string)) {
|
if (!vizsgatargy || !validSubjects.includes(vizsgatargy)) {
|
||||||
return res.status(400).json({ error: 'Érvénytelen vizsgatárgy' })
|
return res.status(400).json({ error: 'Érvénytelen vizsgatárgy' })
|
||||||
}
|
}
|
||||||
|
|
||||||
let honap
|
let honap: string
|
||||||
switch (idoszak) {
|
switch (idoszak) {
|
||||||
case 'osz':
|
case 'osz':
|
||||||
honap = 'okt'
|
honap = 'okt'
|
||||||
|
@ -38,7 +44,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
return res.status(400).json({ error: 'Érvénytelen időszak' })
|
return res.status(400).json({ error: 'Érvénytelen időszak' })
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefix
|
let prefix: string
|
||||||
switch (szint) {
|
switch (szint) {
|
||||||
case 'emelt':
|
case 'emelt':
|
||||||
prefix = `e_${vizsgatargy}`
|
prefix = `e_${vizsgatargy}`
|
||||||
|
@ -54,7 +60,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const utmutato = 'ut'
|
const utmutato = 'ut'
|
||||||
const forras = 'for'
|
const forras = 'for'
|
||||||
const megoldas = 'meg'
|
const megoldas = 'meg'
|
||||||
const shortev = ev!.slice(-2)
|
const shortev = ev.slice(-2)
|
||||||
|
|
||||||
let flPdfUrl, utPdfUrl, flZipUrl, utZipUrl
|
let flPdfUrl, utPdfUrl, flZipUrl, utZipUrl
|
||||||
switch (vizsgatargy) {
|
switch (vizsgatargy) {
|
||||||
|
|
29
src/pages/api/validate.ts
Normal file
29
src/pages/api/validate.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) {
|
||||||
|
const { link } = req.query as { link: string }
|
||||||
|
let MissingParam: string | null = null
|
||||||
|
if (!link) {
|
||||||
|
MissingParam = 'link'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MissingParam) {
|
||||||
|
return res.status(400).json({ error: `Hiányzó paraméter: ${MissingParam}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
const domain = link.split('/')[2]
|
||||||
|
if (domain !== 'dload-oktatas.educatio.hu') {
|
||||||
|
return res.status(400).json({ error: 'Érvénytelen link' })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(link, { method: 'HEAD' })
|
||||||
|
const status = response.status
|
||||||
|
res.status(200).json({ status })
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Hiányzó paraméterek:' })
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ export default function Home() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedLevel && selectedPeriod && selectedSubject && selectedYear) {
|
if (selectedLevel && selectedPeriod && selectedSubject && selectedYear) {
|
||||||
fetchData(
|
void fetchData(
|
||||||
selectedSubject,
|
selectedSubject,
|
||||||
selectedYear,
|
selectedYear,
|
||||||
selectedPeriod,
|
selectedPeriod,
|
||||||
|
|
|
@ -9,25 +9,26 @@ export const fetchData = async (
|
||||||
setutPdfLink: (link: string) => void
|
setutPdfLink: (link: string) => void
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
let url = `/api/erettsegi?vizsgatargy=${selectedSubject}&ev=${selectedYear}&idoszak=${selectedPeriod}&szint=${selectedLevel}`
|
const url = `/api/erettsegi?vizsgatargy=${selectedSubject}&ev=${selectedYear}&idoszak=${selectedPeriod}&szint=${selectedLevel}`
|
||||||
|
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json()
|
const data = (await response.json()) as {
|
||||||
|
flZipUrl: string
|
||||||
|
utZipUrl: string
|
||||||
|
flPdfUrl: string
|
||||||
|
utPdfUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
if (data.utZipUrl && data.flZipUrl) {
|
if (data.utZipUrl && data.flZipUrl) {
|
||||||
setflZipLink(data.flZipUrl)
|
setflZipLink(data.flZipUrl)
|
||||||
setutZipLink(data.utZipUrl)
|
setutZipLink(data.utZipUrl)
|
||||||
} else {
|
|
||||||
console.error('Nincs érvényes ZIP link a válaszban.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.utPdfUrl && data.flPdfUrl) {
|
if (data.utPdfUrl && data.flPdfUrl) {
|
||||||
setflPdfLink(data.flPdfUrl)
|
setflPdfLink(data.flPdfUrl)
|
||||||
setutPdfLink(data.utPdfUrl)
|
setutPdfLink(data.utPdfUrl)
|
||||||
} else {
|
|
||||||
console.error('Nincs érvényes PDF link a válaszban.')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('Hiba történt az API hívás során.')
|
console.error('Hiba történt az API hívás során.')
|
||||||
|
|
|
@ -10,3 +10,8 @@ export interface SelectorProps {
|
||||||
setSelectedPeriod: React.Dispatch<React.SetStateAction<string>>
|
setSelectedPeriod: React.Dispatch<React.SetStateAction<string>>
|
||||||
setSelectedLevel: React.Dispatch<React.SetStateAction<string>>
|
setSelectedLevel: React.Dispatch<React.SetStateAction<string>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ButtonProps {
|
||||||
|
label: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
|
8
src/utils/types.ts
Normal file
8
src/utils/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export type ButtonColor =
|
||||||
|
| 'primary'
|
||||||
|
| 'danger'
|
||||||
|
| 'default'
|
||||||
|
| 'secondary'
|
||||||
|
| 'success'
|
||||||
|
| 'warning'
|
||||||
|
| undefined
|
|
@ -3,9 +3,7 @@ import { nextui } from '@nextui-org/react'
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: [
|
content: [
|
||||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
'./src/**/*.tsx',
|
||||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
||||||
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}',
|
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
|
|
|
@ -1,22 +1,42 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
/* Base Options: */
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "bundler",
|
"target": "es2022",
|
||||||
|
"allowJs": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
|
||||||
|
/* Strictness */
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"checkJs": true,
|
||||||
|
|
||||||
|
/* Bundled projects */
|
||||||
|
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||||
|
"noEmit": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"plugins": [{ "name": "next" }],
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
|
||||||
|
/* Path Aliases */
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": [
|
||||||
|
".eslintrc.cjs",
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
"**/*.cjs",
|
||||||
|
"**/*.js",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue