Vizzuality/landgriffon

View on GitHub
client/src/pages/data/scenarios/[scenarioId]/edit.tsx

Summary

Maintainability
D
2 days
Test Coverage
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;