diff --git a/dmwt_session10/src/components/AddFruit.js b/dmwt_session10/src/components/AddFruit.js index 9b94cd8a519989df8db375a70b2d34ee2a8aa30f..e6d63329aa0bf2fb38f008cd2d6cceaacff5c5bd 100644 --- a/dmwt_session10/src/components/AddFruit.js +++ b/dmwt_session10/src/components/AddFruit.js @@ -1,15 +1,24 @@ import React, { useState } from 'react'; +const fetcher = url => fetch(url).then(res => res.json()); + const AddFruit = () => { + const [germanName, setGermanName] = useState(''); const [latinName, setLatinName] = useState(''); const [color, setColor] = useState(''); const [origin, setOrigin] = useState(''); const [calories, setCalories] = useState(''); + const [error, setError] = useState(''); const handleSubmit = async (e) => { e.preventDefault(); + if (!germanName || !latinName || !color || !origin || !calories) { + setError('Alle Felder sind erforderlich.'); + return; + } + const newFruit = { germanName, latinName, @@ -17,26 +26,37 @@ const AddFruit = () => { origin, calories, }; + const response = await fetch('/api/add-fruit', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newFruit), + }); - const response = await fetch('/api/add-fruit', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(newFruit), - }); + if (response.status === 409) { + + return console.error("Fruit already added"); + } - if (!response.ok) { - console.error('Failed to add fruit'); - return; - } + if (!response.ok) { + const errorData = await response.json(); + setError(errorData.error || 'Failed to add fruit'); + return; + } - const data = await response.json(); - console.log(JSON.stringify(data)); + setGermanName(''); + setLatinName(''); + setColor(''); + setOrigin(''); + setCalories(''); + setError(''); + }; return ( <form onSubmit={handleSubmit}> + {error && <div className="error-message">{error}</div>} <div> <label htmlFor="germanName">Frucht</label> <input diff --git a/dmwt_session10/src/components/FruitList.js b/dmwt_session10/src/components/FruitList.js index 3865c3450a37b7be54cc155ca24306c302690e0b..6e3f4acceb12488a54d893bca2b64c2567d73831 100644 --- a/dmwt_session10/src/components/FruitList.js +++ b/dmwt_session10/src/components/FruitList.js @@ -4,31 +4,67 @@ import useSWR from 'swr'; const fetcher = url => fetch(url).then(res => res.json()); const FruitList = () => { - const { data: fruits, error } = useSWR('/api/list-fruits', fetcher, { + const { data: fruit, error, mutate } = useSWR('/api/list-fruit', fetcher, { revalidateOnFocus: false, revalidateOnReconnect: false, }); + const handleDelete = async (id) => { + const response = await fetch(`/api/delete-fruit?id=${id}`, { + method: 'DELETE', + }); + + if (!response.ok) { + console.error('Failed to delete fruit'); + return; + } + + mutate(); + }; + if (error) { return <p>Failed to fetch</p>; } - if (!fruits) { + if (!fruit) { return <p>Loading fruits...</p>; } return ( - <ul> - {fruits.length > 0 ? ( - fruits.map((fruit, index) => ( - <li key={index}> - Frucht: {fruit['German Name']}, Lateinischer Name: ({fruit['Latin Name']}), Farbe: {fruit.color}, Herkunft: {fruit.origin}, Kalorien pro 100g: {fruit.calories} - </li> - )) - ) : ( - <li>No fruits available</li> - )} - </ul> + <div className="table-container"> + <table> + <thead> + <tr> + <th>Frucht</th> + <th>Lateinischer Name</th> + <th>Farbe</th> + <th>Herkunft</th> + <th>Kalorien pro 100g</th> + <th>Aktionen</th> + </tr> + </thead> + <tbody> + {fruit.length > 0 ? ( + fruit.map((fruit) => ( + <tr key={fruit.id}> + <td>{fruit["German Name"]}</td> + <td>{fruit["Latin Name"]}</td> + <td>{fruit.color}</td> + <td>{fruit.origin}</td> + <td>{fruit.calories}</td> + <td> + <button onClick={() => handleDelete(fruit.id)}>Remove</button> + </td> + </tr> + )) + ) : ( + <tr> + <td colSpan="6">No fruits available</td> + </tr> + )} + </tbody> + </table> + </div> ); }; diff --git a/dmwt_session10/src/components/Navbar.js b/dmwt_session10/src/components/Navbar.js index c43f0530376b0f5b4a00e35a8cfdae522b8afccb..1baaa40456e4d5d298ca1a2641fd5067f83de5c3 100644 --- a/dmwt_session10/src/components/Navbar.js +++ b/dmwt_session10/src/components/Navbar.js @@ -6,17 +6,25 @@ const Navbar = () => { return ( <> - <nav style={{ position: 'fixed', top: 0, left: 0, right: 0, background: '#6B2222', padding: '20px', zIndex: 1000 }}> - <ul style={{ listStyleType: 'none', padding: 0, margin: 0, display: 'flex', justifyContent: 'flex-end' }}> - <li style={{ marginRight: '10px' }}> - <Link href="/" style={{ textDecoration: 'none', color: router.pathname === '/' ? '#6B2222' : 'white', background: router.pathname === '/' ? 'white' : '#6B2222', padding: '5px 10px', borderRadius: '5px' , fontSize: router.pathname === '/' ? '20px' : '15px'}}>Main</Link> + <nav className="navbar"> + <ul className="navList"> + <li className="navItem"> + <Link href="/" legacyBehavior> + <a className={`navLink ${router.pathname === '/' ? 'active' : ''}`}> + Main + </a> + </Link> </li> - <li style={{ marginRight: '10px' }}> - <Link href="/food" style={{ textDecoration: 'none', color: router.pathname === '/food' ? '#6B2222' : 'white' , background: router.pathname === '/food' ? 'white' : '#6B2222', padding: '5px 10px', borderRadius: '5px', fontSize: router.pathname === '/food' ? '20px' : '15px' }}>Food</Link> + <li className="navItem"> + <Link href="/food" legacyBehavior> + <a className={`navLink ${router.pathname === '/food' ? 'active' : ''}`}> + Food + </a> + </Link> </li> </ul> </nav> - <div style={{ height: '50px' }}></div> + <div style={{ height: '70px' }}></div> </> ); }; diff --git a/dmwt_session10/src/pages/API/add-fruit.js b/dmwt_session10/src/pages/API/add-fruit.js index 2fd78ff388484c58e9ba2d79e08cd7a10afa6629..1a743f43cc9319b7822577e8f0ce7b942d5d8656 100644 --- a/dmwt_session10/src/pages/API/add-fruit.js +++ b/dmwt_session10/src/pages/API/add-fruit.js @@ -1,20 +1,34 @@ import { sql } from '@vercel/postgres'; export default async function handler(request, response) { + if (request.method !== 'POST') { + return response.status(405).json({ error: 'Method not allowed' }); + } + try { const { germanName, latinName, color, origin, calories } = request.body; if (!germanName || !latinName || !color || !origin || !calories) { - return response.status(400).json({ error: 'All fields are required' }); + return response.status(400).json({ error: 'Alle Felder sind erforderlich.' }); + } + + const existingFruit = await sql` + SELECT * FROM fruit WHERE "German Name" = ${germanName}; + `; + + if (existingFruit.rows.length > 0) { + + return response.status(409).json({error:'Fruit already added'}) } - await sql` - INSERT INTO fruits ("German Name", "Latin Name", Color, Origin, Calories) - VALUES (${germanName}, ${latinName}, ${color}, ${origin}, ${calories}); + const result = await sql` + INSERT INTO fruit ("German Name", "Latin Name", Color, Origin, Calories) + VALUES (${germanName}, ${latinName}, ${color}, ${origin}, ${calories}) + RETURNING id, "German Name", "Latin Name", Color, Origin, Calories; `; - const { rows: fruits } = await sql`SELECT * FROM fruits;`; - return response.status(200).json(fruits); + const newFruit = result.rows[0]; + return response.status(200).json(newFruit); } catch (error) { console.error('Error during request processing:', error); return response.status(500).json({ error: error.message }); diff --git a/dmwt_session10/src/pages/API/delete-fruit.js b/dmwt_session10/src/pages/API/delete-fruit.js new file mode 100644 index 0000000000000000000000000000000000000000..6c539e09e0a2f04d443b4771d368281f31a8ac2d --- /dev/null +++ b/dmwt_session10/src/pages/API/delete-fruit.js @@ -0,0 +1,26 @@ +import { sql } from '@vercel/postgres'; + +export default async function handler(request, response) { + if (request.method !== 'DELETE') { + return response.status(405).json({ error: 'Method not allowed' }); + } + + const { id } = request.query; + + if (!id) { + return response.status(400).json({ error: 'ID is required' }); + } + + try { + const result = await sql`DELETE FROM fruit WHERE id = ${id} RETURNING *;`; + + if (result.count === 0) { + return response.status(404).json({ error: 'Fruit not found' }); + } + + return response.status(200).json({ message: 'Fruit deleted', fruit: result[0] }); + } catch (error) { + console.error('Error during request processing:', error); + return response.status(500).json({ error: error.message }); + } +} diff --git a/dmwt_session10/src/pages/API/fruit-table.js b/dmwt_session10/src/pages/API/fruit-table.js index 3ac6c9c1e99f1df2028cc7c7407d8e725660cd2c..a0f15a7ab192b857f250b6d41aca5b12f816a861 100644 --- a/dmwt_session10/src/pages/API/fruit-table.js +++ b/dmwt_session10/src/pages/API/fruit-table.js @@ -3,14 +3,15 @@ import { sql } from '@vercel/postgres'; export default async function handler(request, response) { try { const result = await sql` - CREATE TABLE IF NOT EXISTS fruits ( - "German Name" varchar(50) NOT NULL, - "Latin Name" varchar(50) NOT NULL, - Color varchar(50) NOT NULL, - Origin varchar(200) NOT NULL, - Calories int NOT NULL - ); - `; + CREATE TABLE IF NOT EXISTS fruit ( + + id SERIAL PRIMARY KEY, + "German Name" VARCHAR(255), + "Latin Name" VARCHAR(255), + Color VARCHAR(255), + Origin VARCHAR(255), + Calories INT +);` return response.status(200).json({ result }); } catch (error) { console.error('Error creating table:', error); diff --git a/dmwt_session10/src/pages/API/list-fruit.js b/dmwt_session10/src/pages/API/list-fruit.js new file mode 100644 index 0000000000000000000000000000000000000000..56173f2ea8a694eaa24132ce3b2caef791658787 --- /dev/null +++ b/dmwt_session10/src/pages/API/list-fruit.js @@ -0,0 +1,15 @@ +import { sql } from '@vercel/postgres'; + +export default async function handler(request, response) { + if (request.method !== 'GET') { + return response.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { rows: fruit } = await sql`SELECT id, "German Name", "Latin Name", Color, Origin, Calories FROM fruit;`; + return response.status(200).json(fruit); + } catch (error) { + console.error('Error during request processing:', error); + return response.status(500).json({ error: error.message }); + } +} diff --git a/dmwt_session10/src/pages/API/list-fruits.js b/dmwt_session10/src/pages/API/list-fruits.js deleted file mode 100644 index 24227c7e61f79dbac941f589bfe45749d84b3a3b..0000000000000000000000000000000000000000 --- a/dmwt_session10/src/pages/API/list-fruits.js +++ /dev/null @@ -1,11 +0,0 @@ -import { sql } from '@vercel/postgres'; - -export default async function handler(request, response) { - try { - const { rows: fruits } = await sql`SELECT * FROM fruits;`; - return response.status(200).json(fruits); - } catch (error) { - console.error('Error during request processing:', error); - return response.status(500).json({ error: error.message }); - } -} diff --git a/dmwt_session10/src/pages/_app.js b/dmwt_session10/src/pages/_app.js index bc4acbf11da76a6dceec86285cec9bf0c827e6c6..1aecf3ab46e0e418d1309d070f13129463c132f4 100644 --- a/dmwt_session10/src/pages/_app.js +++ b/dmwt_session10/src/pages/_app.js @@ -1,12 +1,11 @@ +import '../styles/styles.css'; + function MyApp({ Component, pageProps }) { return( <Component {...pageProps} /> - ) - - - } + )} export default MyApp diff --git a/dmwt_session10/src/pages/food.js b/dmwt_session10/src/pages/food.js index dc7d2004480e215a5f7d62d6d3ba83744d48e5b3..e90669284d19df6d996e78f63801df3cbfa6b6a6 100644 --- a/dmwt_session10/src/pages/food.js +++ b/dmwt_session10/src/pages/food.js @@ -7,6 +7,7 @@ const Food = () => { <Navbar /> <h1>Fruit List</h1> <FruitList /> + </div> ); diff --git a/dmwt_session10/src/pages/index.js b/dmwt_session10/src/pages/index.js index e067683907a8059bfbe5e50ee0f852339ddc7613..9ad37cc952f76e2d76c54da4c63af349c0e93797 100644 --- a/dmwt_session10/src/pages/index.js +++ b/dmwt_session10/src/pages/index.js @@ -6,7 +6,7 @@ const Home = () => { <div> <Navbar /> <h1>Add a New Fruit</h1> - <AddFruit onFruitAdded={() => mutate('/api/list-fruits')} /> + <AddFruit/> </div> ); }; diff --git a/dmwt_session10/src/pages/post.js b/dmwt_session10/src/pages/post.js deleted file mode 100644 index 2d95c08ab9a2eec99639c288bb5b443a5ba3ad40..0000000000000000000000000000000000000000 --- a/dmwt_session10/src/pages/post.js +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState } from 'react'; -import useSWR from 'swr'; - -const fetcher = url => fetch(url).then(res => res.json()); - -const Post = () => { - const [germanName, setGermanName] = useState(''); - const [latinName, setLatinName] = useState(''); - const [color, setColor] = useState(''); - const [origin, setOrigin] = useState(''); - const [calories, setCalories] = useState(''); - - const { data: fruits, isLoading, error } = useSWR('/api/list-fruits', fetcher, { - revalidateOnFocus: false, - revalidateOnReconnect: false, - }); - - if (error) { - return <p>Failed to fetch</p>; - } - - if (isLoading) { - return <p>Loading fruits...</p>; - } - - const handleSubmit = async (e) => { - e.preventDefault(); - - const newFruit = { - germanName, - latinName, - color, - origin, - calories, - }; - - const response = await fetch('/api/add-fruit', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(newFruit), - }); - - if (!response.ok) { - console.error('Failed to add fruit'); - return; - } - - const data = await response.json(); - console.log(JSON.stringify(data)); - }; - - return ( - <div> - <form onSubmit={handleSubmit}> - <div> - <label htmlFor="germanName">German Name</label> - <input - id="germanName" - type="text" - value={germanName} - onChange={(e) => setGermanName(e.target.value)} - /> - </div> - <div> - <label htmlFor="latinName">Latin Name</label> - <input - id="latinName" - type="text" - value={latinName} - onChange={(e) => setLatinName(e.target.value)} - /> - </div> - <div> - <label htmlFor="color">Color</label> - <input - id="color" - type="text" - value={color} - onChange={(e) => setColor(e.target.value)} - /> - </div> - <div> - <label htmlFor="origin">Origin</label> - <input - id="origin" - type="text" - value={origin} - onChange={(e) => setOrigin(e.target.value)} - /> - </div> - <div> - <label htmlFor="calories">Calories</label> - <input - id="calories" - type="text" - value={calories} - onChange={(e) => setCalories(e.target.value)} - /> - </div> - <button type="submit">Submit</button> - </form> - <ul> - {fruits && fruits.length > 0 ? ( - fruits.map((fruit, index) => ( - <li key={index}> - {fruit.germanName} ({fruit.latinName}), {fruit.color}, {fruit.origin}, {fruit.calories} kcal - </li> - )) - ) : ( - <li>No fruits available</li> - )} - </ul> - </div> - ); -}; - -export default Post; diff --git a/dmwt_session10/src/styles/styles.css b/dmwt_session10/src/styles/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..e11d9f6d4b0f7d981e4e12b52213b40c777b9cd8 --- /dev/null +++ b/dmwt_session10/src/styles/styles.css @@ -0,0 +1,181 @@ +/* General styles */ +body { + background-color: #121212; + color: #e0e0e0; + font-family: 'Arial', sans-serif; + margin: 0; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +a { + color: #90caf9; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +button { + background-color: #333333; + border: none; + color: #e0e0e0; + padding: 10px 20px; + cursor: pointer; + border-radius: 5px; + margin-top: 10px; +} + +button:hover { + background-color: #555555; +} + +form { + background-color: #1f1f1f; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + max-width: 400px; + width: 100%; + box-sizing: border-box; +} + +form div { + margin-bottom: 15px; +} + +label { + display: block; + margin-bottom: 5px; + color: #90caf9; +} + +input { + width: 100%; + padding: 8px; + border: 1px solid #333333; + border-radius: 4px; + background-color: #333333; + color: #e0e0e0; + box-sizing: border-box; +} + +input:focus { + border-color: #90caf9; + outline: none; +} + +/* Table styles */ +.table-container { + width: 90%; + max-width: 800px; + margin: 20px auto; + background-color: #1f1f1f; + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background-color: #333333; +} + +thead th { + padding: 10px; + color: #e0e0e0; + text-align: left; +} + +tbody tr { + border-bottom: 1px solid #444444; +} + +tbody tr:nth-child(even) { + background-color: #2a2a2a; +} + +tbody td { + padding: 10px; + color: #e0e0e0; +} + +tbody tr:hover { + background-color: #3a3a3a; +} + +button { + background-color: #d32f2f; + border: none; + color: #e0e0e0; + padding: 5px 10px; + cursor: pointer; + border-radius: 5px; + margin-top: 10px; +} + +button:hover { + background-color: #e53935; +} + +/* Navbar styles */ +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + background: #2e3b4e; + padding: 15px 20px; + z-index: 1000; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.navList { + list-style-type: none; + padding: 0; + margin: 0; + display: flex; + justify-content: flex-end; +} + +.navItem { + margin-right: 10px; +} + +.navLink { + text-decoration: none; + color: white; + background: #2e3b4e; + padding: 10px 15px; + border-radius: 5px; + transition: background 0.3s, color 0.3s, font-size 0.3s; + font-size: 15px; +} + +.navLink:hover { + background: #3c4f65; +} + +.active { + background: white; + color: #2e3b4e; + font-size: 20px; +} + +.error-message { + color: red; + background-color: #ffe6e6; + border: 1px solid red; + padding: 10px; + margin-bottom: 15px; + border-radius: 5px; +} +