Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Comment" ALTER COLUMN "isReservable" SET DEFAULT false;
2 changes: 1 addition & 1 deletion apps/backend/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"button": "^1.1.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cookies-next": "^6.1.0",
"framer": "^2.4.1",
"framer-motion": "^11.15.0",
"geist": "^1.3.1",
Expand Down
40 changes: 40 additions & 0 deletions apps/frontend/src/app/members/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import { Input } from '@components/ui/input';
import { useEffect, useState } from 'react';

import MemberTile from '@/components/member/memberTile';
import { mockUsers } from '@/mocks/users';

export default function Members() {
const data = mockUsers; //TODO: replace with real data
const [filteredData, setFilteredData] = useState(data);
const [searchTerm, setSearchTerm] = useState('');

useEffect(() => {
setFilteredData(data.filter((user) => user.fullName.toLowerCase().includes(searchTerm.toLowerCase())));
}, [searchTerm]);

return (
<div className='w-full overflow-y-auto'>
<div className='flex items-center justify-between flex-row p-4 sticky top-0 bg-background z-10'>
<h1 className='text-2xl font-semibold text-primary'>Felhasználók</h1>
<Input
placeholder='Keresés...'
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
className='max-w-sm target:ring-0'
/>
</div>
{filteredData.length ? (
<div className='grid gap-4 py-4 auto-rows-fr grid-cols-[repeat(auto-fill,minmax(228px,228px))] justify-center'>
{filteredData.map((user) => (
<MemberTile user={user} key={user.id} />
))}
</div>
) : (
<div className='h-24 flex items-center justify-center text-center'>Nincs találat.</div>
)}
</div>
);
}
140 changes: 140 additions & 0 deletions apps/frontend/src/app/news/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
'use client';

import { Pencil, Trash } from 'lucide-react';
import { useState } from 'react';

import { TextArea } from '@/components/ui/textarea';
import { mockUsers } from '@/mocks/users';
import { User } from '@/types/user';

import { Button } from '../../components/ui/button';
import { Card, CardContent, CardFooter, CardTitle } from '../../components/ui/card';
import { Input } from '../../components/ui/input';
import { posts as mockNewsPosts } from '../../mocks/posts';
import { Post } from '../../types/post';

const POSTS_PER_PAGE = 10;

function NewsForm({
initial,
onSave,
onCancel,
}: {
initial?: Partial<Post>;
onSave: (post: Omit<Post, 'id' | 'createdAt'>) => void;
onCancel: () => void;
}) {
const [title, setTitle] = useState(initial?.title || '');
const [body, setBody] = useState(initial?.body || '');

return (
<form
onSubmit={(e) => {
e.preventDefault();
onSave({ title, body });
}}
className='space-y-2 px-4'
>
<Input placeholder='Title' value={title} onChange={(e) => setTitle(e.target.value)} required />
<TextArea placeholder='Body' value={body} onChange={(e) => setBody(e.target.value)} required rows={3} />
<div className='flex gap-2'>
<Button type='submit'>Save</Button>
<Button type='button' variant='secondary' onClick={onCancel}>
Cancel
</Button>
</div>
</form>
);
}

export default function NewsPage() {
const currentUser: User = mockUsers[1]; //TODO: replace with real user
const isAdmin = currentUser.role === 'ADMIN';
const [posts, setPosts] = useState<Post[]>(mockNewsPosts);
const [page, setPage] = useState(1);
const [editing, setEditing] = useState<Post | null>(null);
const [creating, setCreating] = useState(false);

const totalPages = Math.ceil(posts.length / POSTS_PER_PAGE);
const paginated = posts.slice((page - 1) * POSTS_PER_PAGE, page * POSTS_PER_PAGE);

function handleCreate(post: Omit<Post, 'id' | 'createdAt'>) {
setPosts([
{
id: (Math.random() * 100000).toFixed(0),
title: post.title,
body: post.body,
createdAt: new Date().toISOString(),
},
...posts,
]);
setCreating(false);
setPage(1);
}

function handleEdit(post: Omit<Post, 'id' | 'createdAt'>) {
if (!editing) return;
setPosts(posts.map((p) => (p.id === editing.id ? { ...p, ...post } : p)));
setEditing(null);
}

function handleDelete(id: string) {
setPosts(posts.filter((p) => p.id !== id));
}

return (
<div className='w-full overflow-y-auto'>
<div className='flex items-center justify-between flex-row p-4'>
<h1 className='text-2xl font-semibold text-primary'>Hírek</h1>
{isAdmin && (
<div>
{creating ? (
<NewsForm onSave={handleCreate} onCancel={() => setCreating(false)} />
) : (
<Button onClick={() => setCreating(true)}>Create New Post</Button>
)}
</div>
)}
</div>
<div className='m-4'>
<div className='space-y-4'>
{paginated.map((post) => (
<Card key={post.id} className='relative'>
<CardTitle className='p-4'>{editing?.id === post.id ? 'Szerkesztés' : post.title}</CardTitle>
{editing?.id === post.id && (
<div className='mt-4'>
<NewsForm initial={editing} onSave={handleEdit} onCancel={() => setEditing(null)} />
</div>
)}
{isAdmin && (
<div className='absolute top-4 right-4 flex gap-2'>
<Button size='sm' variant='secondary' onClick={() => setEditing(post)}>
<Pencil className='w-4 h-4' />
</Button>
<Button size='sm' variant='destructive' onClick={() => handleDelete(post.id)}>
<Trash className='w-4 h-4' />
</Button>
</div>
)}
<CardContent className='p-4'>{editing?.id === post.id ? null : post.body}</CardContent>
<CardFooter className='py-2 px-4 text-sm text-gray-500 dark:text-gray-400'>
{editing?.id === post.id ? null : new Date(post.createdAt).toLocaleDateString('hu-HU')}
</CardFooter>
</Card>
))}
</div>
<div className='flex justify-center gap-2 mt-8'>
<Button variant='secondary' disabled={page === 1} onClick={() => setPage(page - 1)}>
Previous
</Button>
<span className='px-2 py-1'>
Page {page} of {totalPages}
</span>
<Button variant='secondary' disabled={page === totalPages} onClick={() => setPage(page + 1)}>
Next
</Button>
</div>
</div>
</div>
);
}
Loading
Loading