Skip to content

feat: add modals to simulation forms page #20

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

Merged
merged 1 commit into from
Jan 19, 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
16 changes: 4 additions & 12 deletions app/components/ParametersForm/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { useRouter } from 'next/navigation';
import { Mock } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import ParametersForm from '.';

vi.mock('next/navigation', () => ({
useRouter: vi.fn(),
}));

describe('ParametersForm Component', () => {
const mockPush = vi.fn();
const mockConfiguration = {
isFieldFocused: false,
stations: [
Expand All @@ -19,7 +12,7 @@ describe('ParametersForm Component', () => {
};

beforeEach(() => {
(useRouter as Mock).mockReturnValue({ push: mockPush });
HTMLDialogElement.prototype.showModal = vi.fn();
vi.clearAllMocks();
sessionStorage.clear();
});
Expand All @@ -30,7 +23,7 @@ describe('ParametersForm Component', () => {
expect(screen.getByLabelText(/Select Stations Configuration/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Car Consumption \(kWh\)/i)).toBeInTheDocument();
expect(screen.getByLabelText(/Arrival Probability Multiplier/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Prepare Simulation/i })).toBeDisabled();
expect(screen.getByRole('button', { name: /Prepare Report/i })).toBeDisabled();
});

it('allows valid input, enables the submit button, and navigates on click', async () => {
Expand All @@ -48,7 +41,7 @@ describe('ParametersForm Component', () => {
target: { value: '100' },
});

const submitButton = screen.getByRole('button', { name: /Prepare Simulation/i });
const submitButton = screen.getByRole('button', { name: /Prepare Report/i });
await waitFor(() => {
expect(submitButton).toBeEnabled();
});
Expand All @@ -62,7 +55,6 @@ describe('ParametersForm Component', () => {
arrivalProbability: 100,
configuredStations: JSON.stringify({ chargingPower: '11', count: 5 }),
});
expect(mockPush).toHaveBeenCalledWith('/simulation/results');
});
});

Expand All @@ -73,7 +65,7 @@ describe('ParametersForm Component', () => {
target: { value: '4' },
});

fireEvent.click(screen.getByRole('button', { name: /Prepare Simulation/i }));
fireEvent.click(screen.getByRole('button', { name: /Prepare Report/i }));

await waitFor(() => {
expect(
Expand Down
129 changes: 74 additions & 55 deletions app/components/ParametersForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client';

import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import Link from 'next/link';
import { useForm } from 'react-hook-form';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { ParametersFormData, parametersFormSchema } from '@/app/formSchemas';
import { Form, FormInput } from '@/app/ui-lib';
import { Form, FormInput, Modal } from '@/app/ui-lib';
import { SIMULATION_RESULTS_URL } from '@/app/constants';

export default function ParametersForm({
Expand All @@ -16,7 +16,7 @@ export default function ParametersForm({
stations: { chargingPower: string; count: number }[];
};
}) {
const router = useRouter();
const simulationModalRef = useRef<HTMLDialogElement>(null);

const {
register,
Expand All @@ -36,64 +36,83 @@ export default function ParametersForm({

const onSubmit = async (data: ParametersFormData) => {
sessionStorage.setItem('simulationParams', JSON.stringify(data));
router.push(SIMULATION_RESULTS_URL);
if (simulationModalRef.current) {
simulationModalRef.current.showModal();
}
};

return (
<Form className="border-t-4 border-brand-blue pt-6" action={handleSubmit(onSubmit)}>
<h2 className="mb-3 text-2xl text-brand-aqua">Simulation Parameters</h2>
<p className="mb-10 leading-7">
Use this form to configure the global simulation settings, including the number of arriving
vehicles, their energy consumption, and the overall behavior of the charging stations.
Adjust these parameters to match real-world scenarios and optimize your infrastructure
planning.
</p>
<div className="flex flex-col gap-10 md:flex-row">
<div className="md:w-1/2 lg:w-1/3">
<FormInput
register={register('configuredStations')}
type="select"
label="Select Stations Configuration"
error={errors.configuredStations}
className="text-sm"
options={stationsConfiguration.stations.map((station, _) => ({
value: JSON.stringify(station),
label: `${station.count} x ${station.chargingPower} kW`,
}))}
/>
<>
<Form className="border-t-4 border-brand-blue pt-6" action={handleSubmit(onSubmit)}>
<h2 className="mb-3 text-2xl text-brand-aqua">Simulation Parameters</h2>
<p className="mb-10 leading-7">
Use this form to configure the global simulation settings, including the number of
arriving vehicles, their energy consumption, and the overall behavior of the charging
stations. Adjust these parameters to match real-world scenarios and optimize your
infrastructure planning.
</p>
<div className="flex flex-col gap-10 md:flex-row">
<div className="md:w-1/2 lg:w-1/3">
<FormInput
register={register('configuredStations')}
type="select"
label="Select Stations Configuration"
error={errors.configuredStations}
className="text-sm"
options={stationsConfiguration.stations.map((station, _) => ({
value: JSON.stringify(station),
label: `${station.count} x ${station.chargingPower} kW`,
}))}
/>
</div>
<div className="md:w-1/2 lg:w-1/3">
<FormInput
register={register('carConsumption')}
type="number"
label="Car Consumption (kWh)"
placeholder="e.g. 18"
error={errors.carConsumption}
className="text-sm"
/>
</div>
<div className="md:w-0 lg:w-1/3"></div>
</div>
<div className="md:w-1/2 lg:w-1/3">
<div className="md:mt-7">
<FormInput
register={register('carConsumption')}
type="number"
label="Car Consumption (kWh)"
placeholder="e.g. 18"
error={errors.carConsumption}
label="Arrival Probability Multiplier (%)"
type="range"
register={register('arrivalProbability')}
min={20}
max={200}
step={20}
defaultValue={100}
error={errors.arrivalProbability}
className="text-sm"
/>
</div>
<div className="md:w-0 lg:w-1/3"></div>
</div>
<div className="md:mt-7">
<FormInput
label="Arrival Probability Multiplier (%)"
type="range"
register={register('arrivalProbability')}
min={20}
max={200}
step={20}
defaultValue={100}
error={errors.arrivalProbability}
className="text-sm"
/>
</div>
<button
className="btn ml-auto mt-10 border-0 bg-brand-aqua text-black hover:bg-brand-light-aqua"
type="submit"
disabled={!isValid}
>
Prepare Simulation
</button>
</Form>
<button
className="btn ml-auto mt-10 border-0 bg-brand-aqua text-black hover:bg-brand-light-aqua"
type="submit"
disabled={!isValid}
>
Prepare Report
</button>
</Form>
<Modal modalRef={simulationModalRef}>
<div className="pb-6 pt-10 sm:mx-10">
<div className="text-center">
<h3 className="mb-4 text-2xl font-bold">Simulation Report is Ready</h3>
<p className="mb-10 text-lg">Your simulation report was successfully generated.</p>

<Link
href={SIMULATION_RESULTS_URL}
className="btn bg-brand-dark-blue hover:bg-brand-blue"
>
Check the report
</Link>
</div>
</div>
</Modal>
</>
);
}
37 changes: 33 additions & 4 deletions app/components/SimulationForms/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
'use client';

import { useState } from 'react';
import { useRef, useState } from 'react';
import SimulationParametersForm from '@/app/components/ParametersForm';
import ChargeStationsForm from '@/app/components/ChargeStationsForm';
import { Modal } from '@/app/ui-lib';

export default function SimulationPage() {
const [showDuplicateModal, setShowDuplicateModal] = useState<boolean>(false);
const addStationModalRef = useRef<HTMLDialogElement>(null);

const [stationsConfiguration, setStationsConfiguration] = useState({
isFieldFocused: false,
stations: [
Expand All @@ -16,13 +20,18 @@ export default function SimulationPage() {

const addStation = (newStation: { chargingPower: string; count: number }) => {
setStationsConfiguration((prev) => {
const isDuplicate = prev.stations.some(
const isStationDuplicate = prev.stations.some(
(station) =>
station.chargingPower === newStation.chargingPower && station.count === newStation.count,
);

if (isDuplicate) {
alert('This station configuration already exists!');
setShowDuplicateModal(isStationDuplicate);

if (addStationModalRef.current) {
addStationModalRef.current.showModal();
}

if (isStationDuplicate) {
return prev;
}

Expand All @@ -38,6 +47,26 @@ export default function SimulationPage() {
<div className="space-y-10">
<ChargeStationsForm addStation={addStation} />
<SimulationParametersForm stationsConfiguration={stationsConfiguration} />
<Modal modalRef={addStationModalRef}>
<div className="mx-10 pb-6 pt-10">
{showDuplicateModal ? (
<div className="text-center">
<h3 className="mb-4 text-2xl font-bold">Configuration already exists</h3>
<p className="text-lg">Please try to add another combination.</p>
</div>
) : (
<div className="text-center">
<h3 className="mb-4 text-2xl font-bold">A new configuration was added</h3>
<p className="text-lg">
A new configuration was added to
<br />
the "Select Stations Configuration" field.
</p>
</div>
)}
</div>
</Modal>
;
</div>
);
}
2 changes: 1 addition & 1 deletion app/ui-lib/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function Modal({
}) {
return (
<dialog ref={modalRef} className="modal">
<div className="modal-box mt-0 bg-brand-blue">
<div className="modal-box mt-0 bg-brand-aqua text-black">
<form method="dialog">
<button className="btn btn-circle btn-ghost btn-sm absolute right-2 top-2">βœ•</button>
</form>
Expand Down
Loading