client/src/pages/data/scenarios/[scenarioId]/edit.tsx
import { useCallback } from 'react';
import classnames from 'classnames';
import toast from 'react-hot-toast';
import Head from 'next/head';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { dehydrate, useQueryClient } from '@tanstack/react-query';
import { PlusIcon, DotsVerticalIcon } from '@heroicons/react/solid';
import { GetServerSideProps } from 'next';
import InfoTooltip from 'components/info-tooltip';
import { useScenario, useUpdateScenario } from 'hooks/scenarios';
import {
useScenarioInterventions,
useUpdateIntervention,
useDeleteIntervention,
} from 'hooks/interventions';
import CleanLayout from 'layouts/clean';
import ScenarioForm from 'containers/scenarios/form';
import { LocationStatus } from 'containers/interventions/enums';
import BackLink from 'components/back-link';
import Loading from 'components/loading';
import Select from 'components/forms/select';
import { Anchor, Button } from 'components/button';
import Input from 'components/forms/input';
import Toggle from 'components/toggle';
import Dropdown from 'components/dropdown';
import Badge from 'components/badge';
import { handleResponseError } from 'services/api';
import { auth } from '@/pages/api/auth/[...nextauth]';
import getQueryClient from '@/lib/react-query';
import type { ScenarioFormData } from 'containers/scenarios/types';
const UpdateScenarioPage: React.FC = () => {
const { query } = useRouter();
const queryClient = useQueryClient();
const { data, isLoading } = useScenario(query?.scenarioId as string, { include: 'user' });
const updateScenario = useUpdateScenario();
const updateIntervention = useUpdateIntervention();
const deleteIntervention = useDeleteIntervention();
// Interventions
const { data: interventions, isLoading: isInterventionsLoading } = useScenarioInterventions({
scenarioId: data?.id,
});
const handleUpdateScenario = useCallback(
(scenarioFormData: ScenarioFormData) => {
updateScenario.mutate(
{ id: data.id, data: scenarioFormData },
{
onSuccess: () => {
toast.success('Your changes were successfully saved.');
queryClient.invalidateQueries(['scenariosList']);
queryClient.invalidateQueries(['scenario', data.id]);
},
onError: handleResponseError,
},
);
},
[data?.id, queryClient, updateScenario],
);
const handleInterventionToggle = useCallback(
(interventionId: string, isActive: boolean) =>
updateIntervention.mutate(
{ id: interventionId, data: { status: isActive ? 'active' : 'inactive' } },
{
onSuccess: () => {
toast.success('Intervention has been successfully updated.');
queryClient.invalidateQueries(['scenarioInterventions']);
},
onError: handleResponseError,
},
),
[queryClient, updateIntervention],
);
const handleDeleteIntervention = useCallback(
(interventionId) => {
deleteIntervention.mutate(interventionId, {
onSuccess: () => {
toast.success('Intervention has been successfully deleted.');
queryClient.invalidateQueries(['scenarioInterventions']);
},
});
},
[queryClient, deleteIntervention],
);
return (
<CleanLayout>
<Head>
<title>Edit scenario | Landgriffon</title>
</Head>
<BackLink href="/data/scenarios" className="mb-6 flex xl:sticky xl:top-0">
Back to scenarios
</BackLink>
<div className="grid grid-cols-12 gap-6">
<div className="col-span-8 col-start-3">
<h1>Edit scenario</h1>
{isLoading && <Loading />}
{!isLoading && data && (
<ScenarioForm
isSubmitting={updateScenario.isLoading}
scenario={data}
onSubmit={handleUpdateScenario}
>
{/* TO-DO: Promote to a specific component */}
<div>
<div className="mb-4 flex items-baseline space-x-1">
<h2>Growth rates</h2>
<InfoTooltip info="Growth rates set your expectations of how purchases of raw materials will change into the future. Add a new rule to get started." />
</div>
<div className="grid grid-cols-8 gap-4">
<div className="col-span-4">
<label className="mb-2 block text-sm">Business unit</label>
<Select value={null} disabled options={[]} onChange={null} />
</div>
<div className="col-span-2">
<label className="mb-2 block text-sm">Growth rate (linear)</label>
<Input name="growth-rate" placeholder="0 % per year" disabled />
</div>
<div className="col-span-2 flex items-end">
<Button disabled variant="secondary" className="h-[40px] w-full">
Add growth rate
</Button>
</div>
</div>
<div className="mb-2 mt-4 flex flex-wrap gap-2 rounded-md border border-gray-300 p-2">
<Badge className="border-blue-400 bg-blue-200 text-sm">
Entire company +1.5%/y
</Badge>
</div>
<p className="mx-2 text-xs">
Add as many as you want, more specific growth rates override less specific ones.
</p>
</div>
<div>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-baseline space-x-1">
<h2 className="flex-1">Interventions</h2>
<InfoTooltip info="Each intervention is a specific change in sourcing. Create an intervention to get started." />
</div>
{!isInterventionsLoading && interventions.length > 0 && (
<Anchor
href={`/data/scenarios/${data.id}/interventions/new`}
variant="secondary"
className="text-gray-900"
icon={
<div
aria-hidden="true"
className="flex h-5 w-5 items-center justify-center rounded-full bg-navy-400"
>
<PlusIcon className="h-4 w-4 text-white" />
</div>
}
>
Add intervention
</Anchor>
)}
</div>
{isInterventionsLoading && <Loading />}
{!isInterventionsLoading && interventions.length > 0 && (
<div className="space-y-2">
{interventions.map((intervention) => (
<div
key={intervention.id}
className={classnames('flex items-center space-x-4 rounded-md p-4', {
'bg-gray-100': intervention.status === LocationStatus.inactive,
'bg-orange-50': intervention.status === LocationStatus.active,
})}
>
<div className="flex flex-1 space-x-2">
<Toggle
data-interventionId={intervention.id}
active={intervention.status === LocationStatus.active}
onChange={(isActive) =>
handleInterventionToggle(intervention.id, isActive)
}
/>
<div>{intervention.title}</div>
</div>
<Dropdown>
<Dropdown.Button>
<DotsVerticalIcon className="h-4 w-4" />
</Dropdown.Button>
<Dropdown.Items>
<Dropdown.Item>
<Link
className="block px-3 py-2 text-sm"
href={`/data/scenarios/${data.id}/interventions/${intervention.id}/edit`}
>
Edit
</Link>
<button
type="button"
className="block px-3 py-2 text-sm"
onClick={() => handleDeleteIntervention(intervention.id)}
>
Delete
</button>
</Dropdown.Item>
</Dropdown.Items>
</Dropdown>
</div>
))}
</div>
)}
{!isInterventionsLoading && interventions.length === 0 && (
<div className="space-y-6 rounded-md bg-gray-100 px-10 py-16 text-center">
<p className="text-sm">
Each scenario should be formed by at least one intervention in your supply
chain.
</p>
<Anchor
href={`/data/scenarios/${data.id}/interventions/new`}
variant="secondary"
className="text-gray-900"
icon={
<div
aria-hidden="true"
className="flex h-5 w-5 items-center justify-center rounded-full bg-navy-400"
>
<PlusIcon className="h-4 w-4 text-white" />
</div>
}
>
Add intervention
</Anchor>
</div>
)}
</div>
</ScenarioForm>
)}
</div>
</div>
</CleanLayout>
);
};
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await auth(ctx.req, ctx.res);
const queryClient = getQueryClient();
queryClient.setQueryData(['profile', session.accessToken], session.user);
return {
props: {
session,
dehydratedState: dehydrate(queryClient),
},
};
};
export default UpdateScenarioPage;