Skip to content

Commit

Permalink
Merge pull request #269 from dump-hr/events-admin
Browse files Browse the repository at this point in the history
Events admin
  • Loading branch information
bdeak4 authored Mar 12, 2024
2 parents 2142781 + e945540 commit 82669db
Show file tree
Hide file tree
Showing 17 changed files with 307 additions and 10 deletions.
2 changes: 2 additions & 0 deletions apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Route, Switch } from 'wouter';
import { Layout } from './components/Layout';
import { Path } from './constants/paths';
import { CompanyPage } from './pages/CompanyPage';
import EventPage from './pages/EventPage';
import { HomePage } from './pages/HomePage';
import { InterestPage } from './pages/InterestPage';

Expand All @@ -21,6 +22,7 @@ export const App = () => {
<Route path={Path.Home} component={HomePage} />
<Route path={Path.Company} component={CompanyPage} />
<Route path={Path.Interest} component={InterestPage} />
<Route path={Path.Event} component={EventPage} />
</Switch>
</Layout>
<Toaster />
Expand Down
23 changes: 23 additions & 0 deletions apps/admin/src/api/event/useEventCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { EventDto, EventModifyDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const eventCreate = async (dto: EventModifyDto) => {
return await api.post<EventModifyDto, EventDto>('/event', dto);
};

export const useEventCreate = () => {
const queryClient = useQueryClient();

return useMutation(eventCreate, {
onSuccess: () => {
queryClient.invalidateQueries(['event']);
toast.success('Event uspješno dodan!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
12 changes: 12 additions & 0 deletions apps/admin/src/api/event/useEventGetAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { EventDto } from '@ddays-app/types';
import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const eventGetAll = () => {
return api.get<never, EventDto[]>('event');
};

export const useEventGetAll = (options?: QueryOptions<EventDto[]>) => {
return useQuery(['event'], eventGetAll, options);
};
18 changes: 18 additions & 0 deletions apps/admin/src/api/event/useEventGetOne.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { EventDto } from '@ddays-app/types';
import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const eventGetOne = async (id: number) => {
return await api.get<never, EventDto>(`/event/${id}`);
};

export const useEventGetOne = (
id?: number,
options?: QueryOptions<EventDto>,
) => {
return useQuery(['event', id], () => eventGetOne(id!), {
enabled: !!id,
...options,
});
};
23 changes: 23 additions & 0 deletions apps/admin/src/api/event/useEventRemove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { EventDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const eventRemove = async (id: number) => {
return await api.delete<never, EventDto>(`/event/${id}`);
};

export const useEventRemove = () => {
const queryClient = useQueryClient();

return useMutation(eventRemove, {
onSuccess: () => {
queryClient.invalidateQueries(['event ']);
toast.success('Event uspješno uklonjen!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
27 changes: 27 additions & 0 deletions apps/admin/src/api/event/useEventUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { EventDto, EventModifyDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const eventUpdate = async (dto: EventModifyDto & { id: number }) => {
return await api.patch<EventModifyDto, EventDto>(`/event/${dto.id}`, {
...dto,
id: undefined,
});
};

export const useEventUpdate = () => {
const queryClient = useQueryClient();

return useMutation(eventUpdate, {
onSuccess: (updatedEvent) => {
queryClient.invalidateQueries(['event']);
queryClient.invalidateQueries(['event', updatedEvent.id]);
toast.success('Uređivanje eventa uspješno izvršeno!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
2 changes: 1 addition & 1 deletion apps/admin/src/api/interest/useInterestUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const useInterestUpdate = () => {
onSuccess: (updatedInterest) => {
queryClient.invalidateQueries(['interest']);
queryClient.invalidateQueries(['interest', updatedInterest.id]);
toast.success('Uređivanje interesa uspješno uređeno!');
toast.success('Uređivanje interesa uspješno izvršeno!');
},
onError: (error: string) => {
toast.error(error);
Expand Down
1 change: 1 addition & 0 deletions apps/admin/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const navLinks = [
{ href: Path.Home, text: 'Home' },
{ href: Path.Company, text: 'Company' },
{ href: Path.Interest, text: 'Interest' },
{ href: Path.Event, text: 'Event' },
];

export const Layout: React.FC<LayoutProps> = ({ children }) => {
Expand Down
1 change: 1 addition & 0 deletions apps/admin/src/constants/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum Path {
Company = '/admin/company',
Interest = '/admin/interest',
CatchAll = '/admin/:path*',
Event = '/admin/event',
}
108 changes: 108 additions & 0 deletions apps/admin/src/forms/EventForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { EventModifyDto, EventType, Theme } from '@ddays-app/types';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { useForm } from 'react-hook-form';

import { useEventCreate } from '../api/event/useEventCreate';
import { useEventGetOne } from '../api/event/useEventGetOne';
import { useEventUpdate } from '../api/event/useEventUpdate';
import { Button } from '../components/Button';
import { InputHandler } from '../components/InputHandler';
import { Question, QuestionType } from '../types/question';

type EventFormProps = {
id?: number;
onSuccess: () => void;
};

export const EventForm: React.FC<EventFormProps> = ({ id, onSuccess }) => {
const { data: event, isLoading } = useEventGetOne(id);

const createEvent = useEventCreate();
const updateEvent = useEventUpdate();

const questions: Question[] = [
{
id: 'name',
type: QuestionType.Field,
title: 'Naziv',
defaultValue: event?.name,
},
{
id: 'description',
type: QuestionType.TextArea,
title: 'Opis',
defaultValue: event?.description,
},
{
id: 'type',
type: QuestionType.Select,
title: 'Tip',
options: Object.values(EventType),
defaultValue: event?.type,
},
{
id: 'theme',
type: QuestionType.Select,
title: 'Područje interesa',
options: Object.values(Theme),
defaultValue: event?.theme,
},
{
id: 'startsAt',
type: QuestionType.DateTime,
title: 'Početak',
defaultValue: event?.startsAt,
},
{
id: 'endsAt',
type: QuestionType.DateTime,
title: 'Kraj',
defaultValue: event?.endsAt,
},
{
id: 'requirements',
type: QuestionType.TextArea,
title: 'Zahtjevi',
defaultValue: event?.requirements,
},
{
id: 'footageLink',
type: QuestionType.Field,
title: 'Link za sinmke',
defaultValue: event?.footageLink,
},
{
id: 'maxParticipants',
type: QuestionType.Number,
defaultValue: event?.maxParticipants,
},
];

const form = useForm<EventModifyDto>({
resolver: classValidatorResolver(EventModifyDto),
});

if (id && isLoading) {
return <div>Loading...</div>;
}

return (
<>
{questions.map((q) => {
return <InputHandler question={q} form={form} key={q.id} />;
})}

<Button
onClick={form.handleSubmit(async (formData) => {
if (id) {
await updateEvent.mutateAsync({ ...formData, id });
} else {
await createEvent.mutateAsync(formData);
}
onSuccess();
})}>
Submit
</Button>
</>
);
};
67 changes: 67 additions & 0 deletions apps/admin/src/pages/EventPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from 'react';

import { useEventGetAll } from '../api/event/useEventGetAll';
import { useEventRemove } from '../api/event/useEventRemove';
import { Button } from '../components/Button';
import { Modal } from '../components/Modal';
import { Table } from '../components/Table';
import { EventForm } from '../forms/EventForm';

const EventPage = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [eventToEditId, setEventToEditId] = useState<number>();

const events = useEventGetAll();

const removeEvent = useEventRemove();

if (events.isLoading) {
return <div>Loading...</div>;
}

return (
<>
<Modal
isOpen={isModalOpen}
onClose={() => {
setIsModalOpen(false);
setEventToEditId(undefined);
}}>
<EventForm
id={eventToEditId}
onSuccess={() => {
setIsModalOpen(false);
setEventToEditId(undefined);
}}
/>
</Modal>

<Button variant='primary' onClick={() => setIsModalOpen(true)}>
New
</Button>

<Table
data={events.data}
actions={[
{
label: 'Uredi',
action: (event) => {
setEventToEditId(event.id);
setIsModalOpen(true);
},
},
{
label: 'Obriši',
action: (event) => {
if (confirm('Jesi li siguran?')) {
removeEvent.mutateAsync(event.id);
}
},
},
]}
/>
</>
);
};

export default EventPage;
4 changes: 2 additions & 2 deletions apps/api/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ export const event = pgTable('event', {
description: text('description'),
type: eventType('type'),
theme: theme('theme'),
startsAt: timestamp('starts_at').notNull(),
endsAt: timestamp('ends_at').notNull(),
startsAt: text('starts_at').notNull(),
endsAt: text('ends_at').notNull(),
requirements: text('requirements'),
footageLink: text('footage_link'),
maxParticipants: integer('max_participants'),
Expand Down
Empty file.
5 changes: 5 additions & 0 deletions apps/web/src/components/Schedule/Schedule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Schedule = () => {
return <></>;
};

export default Schedule;
3 changes: 3 additions & 0 deletions apps/web/src/components/Schedule/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Schedule from './Schedule';

export default Schedule;
8 changes: 7 additions & 1 deletion apps/web/src/pages/LandingPage/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import Schedule from '../../components/Schedule';

export const LandingPage: React.FC = () => {
return <div>landing</div>;
return (
<>
<Schedule />
</>
);
};
13 changes: 7 additions & 6 deletions packages/types/src/dto/event.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
IsDate,
IsDateString,
IsEnum,
IsNumber,
IsOptional,
Expand All @@ -11,8 +12,8 @@ export type EventDto = {
id: number;
name: string;
description?: string;
startsAt: Date;
endsAt: Date;
startsAt: string;
endsAt: string;
maxParticipants?: number;
requirements?: string;
footageLink?: string;
Expand All @@ -35,11 +36,11 @@ export class EventModifyDto {
@IsEnum(Theme)
theme: Theme;

@IsDate()
startsAt: Date;
@IsDateString()
startsAt: string;

@IsDate()
endsAt: Date;
@IsDateString()
endsAt: string;

@IsOptional()
@IsString()
Expand Down

0 comments on commit 82669db

Please sign in to comment.