Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/providers #26

Merged
merged 6 commits into from
Jan 3, 2025
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
4 changes: 3 additions & 1 deletion components/molecules/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState } from 'react';
import { DesktopHeader, MobileHeader } from './components';
import Container from '../../organisms/Container';
import { useAuthenticatedUser } from '../../../hooks/useAuthenticatedUser';
import { UserRole } from '@prisma/client';

export function Header({
onMenuOpening: handleMenuOpening,
Expand All @@ -23,7 +24,8 @@ export function Header({
];

if (!!user) {
items.push({ href: '/admin/sampling-points', children: 'Admin' });
let href = user.role === UserRole.PROVIDER ? '/admin/lanchas' : '/admin/sampling-points';
items.push({ href, children: 'Admin' });
}

const handleToggle = () => {
Expand Down
240 changes: 240 additions & 0 deletions components/organisms/Admin/LanchasAdmin/LanchasDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { ArrowLeftIcon, CheckIcon, XMarkIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import Link from 'next/link';
import React, { useState } from 'react';
import { z } from 'zod';
import checkErrors from '../../../../utils/checkErrors';
import { GetDeviceResponse } from '../../../../model/device';
import InputText from '../../../molecules/Input/InputText';
import { Button } from '../../../molecules/Buttons/Button';
import { IconTrash } from '../../../../assets/icons';
import { ResponseModal } from '../../ResponseModal';

const deviceSchema = z.object({
name: z.string().min(3, 'El nombre es requerido').max(100, 'El nombre puede tener un máximo de 100 caracteres'),
description: z.string().min(3, 'La descripción es requerida').max(280, 'La descripción puede tener un máximo de 280 caracteres').optional(),
});

type Data = z.infer<typeof deviceSchema>;

type Errors = Record<keyof Data, string>;

type Reducer = (
state: Data,
action: { type: string; payload: Data[keyof Data] }
) => Data;

interface DeviceDetailProps {
type: 'create' | 'update';
device?: GetDeviceResponse;
isAbleToPerformActions?: boolean;
}

const reducer: Reducer = (state, action) => {
return {
...state,
[action.type]: action.payload,
};
};

const initialErrors: Errors = {
name: '',
description: '',
};

const LanchasDetail: React.FC<DeviceDetailProps> = ({ device: foundDevice, type, isAbleToPerformActions }) => {
const initialData: Data = {
create: {
name: '',
description: '',
},
update: {
name: foundDevice?.name || '',
description: foundDevice?.description || '',
},
}[type];

const [data, setData] = React.useReducer(reducer, initialData);
const [errors, setErrors] = React.useState<Errors>(initialErrors);
const [showModal, setShowModal] = useState(false);
const [successMessage, setSuccessMessage] = useState('');

const handleSubmitCreate = async () => {

const errors = checkErrors(deviceSchema, data, initialErrors);

if (Object.values(errors).some((error) => error !== '')) {
setErrors(errors);
return;
}

try {
await axios.post('/api/lanchas', {
...data,
});
setSuccessMessage('Lancha creada exitosamente.');
setShowModal(true);
} catch (error) {
window.alert('Error al crear la lancha');
}
};

const handleSubmitUpdate = async () => {
const errors = checkErrors(deviceSchema, data, initialErrors);

if (Object.values(errors).some((error) => error !== '')) {
setErrors(errors);
return;
}

try {
await axios.put(`/api/lanchas/${foundDevice?.id}`, {
...data,
});
setSuccessMessage('Lancha actualizada exitosamente.');
setShowModal(true);
} catch (error) {
window.alert('Error al actualizar la lancha');
}
};

const handleSubmit = {
create: handleSubmitCreate,
update: handleSubmitUpdate,
}[type];

const handleDeleteDevice = async () => {
try {
await axios.delete(`/api/lanchas/${foundDevice?.id}`);
setSuccessMessage('Lancha eliminada exitosamente.');
setShowModal(true);
} catch (error) {
window.alert('Error al eliminar la lancha');
}
}

return (
<form className="flex flex-col space-y-8">
<div className="flex flex-col space-y-8">
{type === "update" && isAbleToPerformActions ?
<div className='flex justify-end'>
<Button
onClick={handleDeleteDevice}
variant="primary-admin"
iconSize='xxs'
icon={<IconTrash />}
iconColor={'danger'}
className={'!text-danger'}
>
Eliminar Lancha
</Button>
</div> : ''
}
<div className="flex flex-col lg:flex-row space-x-0 lg:space-x-6 lg:space-y-0 space-y-3">
<InputText
label="Nombre"
value={data.name}
error={errors.name}
variant='admin-2'
disabled={type === "update" && !isAbleToPerformActions}
onChange={(e) => {
setData({
type: 'name',
payload: e.target.value,
});
}}
/>
{type == "update" && (
<>
<InputText
label='ID'
variant='admin-2'
value={foundDevice?.id}
disabled={true}
allWrapperClassName='w-1/5'
/>
</>
)}
</div>

<div className='w-3/5 space-y-4'>
<InputText
label="Descripción"
variant='admin-2'
disabled={type === "update" && !isAbleToPerformActions}
value={data.description}
error={errors.description}
onChange={(e) => {
setData({
type: 'description',
payload: e.target.value,
});
}}
textarea
/>
</div>

{type == "update" &&
<div className="flex flex-col lg:flex-row space-x-0 lg:space-x-6 space-y-3 lg:space-y-0">
<InputText
label="Propietario / Owner"
value={foundDevice?.owner.firstName + ' ' + foundDevice?.owner.lastName}
variant='admin-2'
disabled={true}
/>
<InputText
label="Organización"
value={foundDevice?.owner.organizationName}
variant='admin-2'
disabled={true}
/>
<InputText
label="Mail de contacto"
value={foundDevice?.owner.email}
variant='admin-2'
disabled={true}
/>
</div>
}
</div>

<div className="flex flex-col lg:flex-row space-x-0 lg:space-x-6 space-y-3 lg:space-y-0">
{type !== "update" || isAbleToPerformActions ?
<>
<Link href="/admin/lanchas">
<Button icon={<XMarkIcon />}
iconSize="small"
variant="secondary"
className='w-full lg:w-32'>Cancelar</Button>
</Link>

<Button
icon={<CheckIcon />}
iconSize="small"
className='w-full lg:w-32'
onClick={() => handleSubmit()}
>
{type === 'create' ? 'Crear' : 'Confirmar'}
</Button>
</> :
<Link href="/admin/lanchas">
<Button
icon={<ArrowLeftIcon />}
iconSize="small"
variant="primary-admin"
className='w-full lg:w-32'>Volver</Button>
</Link>
}
</div>
{showModal && (
<ResponseModal
showModal={showModal}
message={successMessage}
routePathname={'/admin/lanchas/'}
/>
)}
</form>
);
};

export default LanchasDetail;
13 changes: 10 additions & 3 deletions components/organisms/Layout/AdminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import {
ArrowLeftIcon,
BookmarkIcon,
MapPinIcon,
NewspaperIcon,
UserCircleIcon,
Squares2X2Icon,
DocumentPlusIcon,
UsersIcon,
Expand All @@ -26,13 +24,22 @@ let links: SidebarLinkProps[] = [
text: 'Puntos de toma',
icon: <MapPinIcon />,
selected: false,
restrictedTo: [UserRole.ADMIN, UserRole.COLLABORATOR],
},
{
href: '/admin/modulos',
text: 'Módulos',
icon: <Squares2X2Icon />,
selected: false,
},
restrictedTo: [UserRole.ADMIN, UserRole.COLLABORATOR],
},
{
href: '/admin/lanchas',
text: 'Lanchas',
icon: <MapPinIcon />,
selected: false,
restrictedTo: [UserRole.PROVIDER],
},
{
href: '/admin/news-posts',
text: 'Noticias',
Expand Down
18 changes: 14 additions & 4 deletions middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,22 @@ const routeConfig: Config[] = [
{
path: '/api/devices',
checkAsRegex: false,
roles: [UserRole.ADMIN, UserRole.COLLABORATOR],
roles: [UserRole.ADMIN, UserRole.COLLABORATOR, UserRole.PROVIDER],
},
{
path: '/admin/devices',
checkAsRegex: false,
roles: [UserRole.ADMIN, UserRole.COLLABORATOR],
roles: [UserRole.ADMIN, UserRole.COLLABORATOR, UserRole.PROVIDER],
},
{
path: '/api/lanchas',
checkAsRegex: false,
roles: [UserRole.ADMIN, UserRole.PROVIDER],
},
{
path: '/admin/lanchas',
checkAsRegex: false,
roles: [UserRole.ADMIN, UserRole.PROVIDER],
},
// related to External Samples
{
Expand Down Expand Up @@ -118,7 +128,7 @@ const routeConfig: Config[] = [
{
path: '/admin/sampling-points',
checkAsRegex: false,
roles: [UserRole.ADMIN, UserRole.COLLABORATOR],
roles: [UserRole.ADMIN, UserRole.COLLABORATOR, UserRole.PROVIDER],
},
{
path: '/api/sampling-points',
Expand All @@ -139,7 +149,7 @@ const routeConfig: Config[] = [
{
path: '/api/admin/users',
checkAsRegex: false,
roles: [UserRole.ADMIN, UserRole.COLLABORATOR],
roles: [UserRole.ADMIN, UserRole.COLLABORATOR, UserRole.PROVIDER],
},
// related to Changelog
{
Expand Down
50 changes: 50 additions & 0 deletions pages/admin/lanchas/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { GetServerSideProps, NextPage, InferGetServerSidePropsType } from 'next';
import axiosFromServerSideProps from '../../../utils/axiosFromServerSideProps';
import { GetDeviceResponse } from '../../../model/device';
import AdminLayout from '../../../components/organisms/Layout/AdminLayout';
import { useAuthenticatedUser } from '../../../hooks/useAuthenticatedUser';
import { UserRole } from '@prisma/client';
import LanchasDetail from '../../../components/organisms/Admin/LanchasAdmin/LanchasDetail';

interface ServerSideProps {
device: GetDeviceResponse;
}

export const getServerSideProps: GetServerSideProps<ServerSideProps> = async (
ctx
) => {

try {
const device: GetDeviceResponse = await (await axiosFromServerSideProps(ctx))
.get(`/api/lanchas/${ctx.query.id}`)
.then((res) => res.data);

return {
props: {
device: JSON.parse(JSON.stringify(device)),
},
};
} catch (error) {
console.error(error);
return {
props: { device: null },
};
}
};

const EditLancha: NextPage<ServerSideProps> = ({ device }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
const user = useAuthenticatedUser();

const isUserOwner = device?.owner.id === user?.id;
const isUserAdmin = user?.role === UserRole.ADMIN
const isAbleToPerformActions = isUserAdmin || isUserOwner;
return (
<AdminLayout title="Editar Lancha">
{device ?
<LanchasDetail type="update" device={device} isAbleToPerformActions={isAbleToPerformActions} />
: <h1>Error</h1>}
</AdminLayout>
);
};

export default EditLancha;
Loading
Loading