Skip to content

Commit

Permalink
Merge pull request #204 from dump-hr/admin-interest-page
Browse files Browse the repository at this point in the history
Admin interest page
  • Loading branch information
bdeak4 authored Feb 14, 2024
2 parents b0c154c + 5103bc1 commit 9482d22
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 3 deletions.
2 changes: 2 additions & 0 deletions apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Layout } from './components/Layout';
import { Path } from './constants/paths';
import { CompanyPage } from './pages/CompanyPage';
import { HomePage } from './pages/HomePage';
import { InterestPage } from './pages/InterestPage';

export const App = () => {
useMsalAuthentication(InteractionType.Redirect);
Expand All @@ -19,6 +20,7 @@ export const App = () => {
<Switch>
<Route path={Path.Home} component={HomePage} />
<Route path={Path.Company} component={CompanyPage} />
<Route path={Path.Interest} component={InterestPage} />
</Switch>
</Layout>
<Toaster />
Expand Down
23 changes: 23 additions & 0 deletions apps/admin/src/api/interest/useInterestCreate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { InterestDto, InterestModifyDto } from '@ddays-app/types';
import toast from 'react-hot-toast';
import { useMutation, useQueryClient } from 'react-query';

import { api } from '..';

const interestCreate = async (dto: InterestModifyDto) => {
return await api.post<InterestModifyDto, InterestDto>('/interest', dto);
};

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

return useMutation(interestCreate, {
onSuccess: () => {
queryClient.invalidateQueries(['interest']);
toast.success('Interes uspješno dodan!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
4 changes: 2 additions & 2 deletions apps/admin/src/api/interest/useInterestGetAll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { QueryOptions, useQuery } from 'react-query';

import { api } from '..';

const interestGetAll = () => {
return api.get<never, InterestDto[]>('/interest');
const interestGetAll = async () => {
return await api.get<never, InterestDto[]>('/interest');
};

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

import { api } from '..';

const interestGetOne = async (id: number) => {
return await api.get<never, InterestDto>(`/interest/${id}`);
};

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

import { api } from '..';

const interestRemove = async (id: number) => {
return await api.delete<never, InterestDto>(`/interest/${id}`);
};

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

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

import { api } from '..';

const interestUpdate = async (dto: InterestModifyDto & { id: number }) => {
return await api.patch<InterestModifyDto, InterestDto>(
`/interest/${dto.id}`,
{
...dto,
id: undefined,
},
);
};

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

return useMutation(interestUpdate, {
onSuccess: (updatedInterest) => {
queryClient.invalidateQueries(['interest']);
queryClient.invalidateQueries(['interest', updatedInterest.id]);
toast.success('Uređivanje interesa uspješno uređeno!');
},
onError: (error: string) => {
toast.error(error);
},
});
};
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 @@ -12,6 +12,7 @@ type LayoutProps = {
const navLinks = [
{ href: Path.Home, text: 'Home' },
{ href: Path.Company, text: 'Company' },
{ href: Path.Interest, text: 'Interest' },
];

export const Layout: React.FC<LayoutProps> = ({ children }) => {
Expand Down
69 changes: 69 additions & 0 deletions apps/admin/src/forms/InterestForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { InterestModifyDto, Theme } from '@ddays-app/types';
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
import { useForm } from 'react-hook-form';

import { useInterestCreate } from '../api/interest/useInterestCreate';
import { useInterestGetOne } from '../api/interest/useInterestGetOne';
import { useInterestUpdate } from '../api/interest/useInterestUpdate';
import { Button } from '../components/Button';
import { InputHandler } from '../components/InputHandler';
import { Question, QuestionType } from '../types/question';

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

export const InterestForm: React.FC<InterestFormProps> = ({
id,
onSuccess,
}) => {
const { data: interest, isLoading } = useInterestGetOne(id);

const updateInterest = useInterestUpdate();
const createInterest = useInterestCreate();

const questions: Question[] = [
{
id: 'name',
type: QuestionType.Field,
title: 'Ime',
defaultValue: interest?.name,
},
{
id: 'theme',
type: QuestionType.Select,
title: 'Područje interesa',
options: Object.values(Theme),
defaultValue: interest?.theme,
},
];

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

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

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

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

import { useInterestGetAll } from '../api/interest/useInterestGetAll';
import { useInterestRemove } from '../api/interest/useInterestRemove';
import { Button } from '../components/Button';
import { Modal } from '../components/Modal';
import { Table } from '../components/Table';
import { InterestForm } from '../forms/InterestForm';

export const InterestPage = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [interestToEditId, setInterestToEditId] = useState<number>();

const interests = useInterestGetAll();

const removeInterest = useInterestRemove();

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

return (
<>
<Modal
isOpen={isModalOpen}
onClose={() => {
setIsModalOpen(false);
setInterestToEditId(undefined);
}}>
<InterestForm
id={interestToEditId}
onSuccess={() => {
setIsModalOpen(false);
setInterestToEditId(undefined);
}}
/>
</Modal>

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

<Table
data={interests.data}
actions={[
{
label: 'Uredi',
action: (interest) => {
setInterestToEditId(interest.id);
setIsModalOpen(true);
},
},
{
label: 'Obriši',
action: (interest) => {
if (confirm('Jesi li siguran?')) {
removeInterest.mutateAsync(interest.id);
}
},
},
]}
/>
</>
);
};
5 changes: 5 additions & 0 deletions apps/api/src/interest/interest.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export class InterestController {
return await this.interestService.create(dto);
}

@Get(':id')
async getOne(@Param('id', ParseIntPipe) id: number): Promise<InterestDto> {
return await this.interestService.getOne(id);
}

@Get()
async getAll(): Promise<InterestDto[]> {
return await this.interestService.getAll();
Expand Down
15 changes: 14 additions & 1 deletion apps/api/src/interest/interest.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InterestDto, InterestModifyDto } from '@ddays-app/types';
import { Injectable } from '@nestjs/common';
import { Injectable, NotFoundException } from '@nestjs/common';
import { db } from 'db';
import { companyToInterest, interest } from 'db/schema';
import { eq, inArray } from 'drizzle-orm';
Expand Down Expand Up @@ -55,6 +55,19 @@ export class InterestService {
return createdInterest;
}

async getOne(id: number): Promise<InterestDto> {
const [foundInterest] = await db
.select()
.from(interest)
.where(eq(interest.id, id));

if (!foundInterest) {
throw new NotFoundException('Interest not found');
}

return foundInterest;
}

async getAll(): Promise<InterestDto[]> {
const interests = await db
.select({
Expand Down

0 comments on commit 9482d22

Please sign in to comment.