Skip to content

Commit

Permalink
Merge pull request #28 from AntaresSimulatorTeam/feature/ANT-2478_cre…
Browse files Browse the repository at this point in the history
…ate_study

Feature/ant 2478 create study
  • Loading branch information
melazaar authored Jan 7, 2025
2 parents d597de5 + 02655b2 commit fd063b0
Show file tree
Hide file tree
Showing 15 changed files with 619 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@layer rte-design-system-react
@tailwind base;
@tailwind components;
@tailwind utilities;

Expand Down
2 changes: 1 addition & 1 deletion src/envVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type EnvVariableType = {

// Environment Variable Template to Be Replaced at Runtime
export const envVariables: EnvVariableType = {
VITE_BACK_END_BASE_URL: 'http://pegase-integration.rte-france.com:8080',
VITE_BACK_END_BASE_URL: '${URL_BACKEND}',
};
export const getEnvVariables = (key: keyof EnvVariableType) =>
envVariables[key].startsWith('$') ? (import.meta.env[key] as string) : envVariables[key];
55 changes: 55 additions & 0 deletions src/hooks/useFocusTrapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { useEffect } from 'react';

const FOCUSABLE_ELEMENTS = [
'button',
'a[href]',
'input',
'select',
'textarea',
'details',
'[tabindex]:not([tabindex="-1"])',
];
const FOCUSABLE_ELEMENTS_QUERY = FOCUSABLE_ELEMENTS.map((elmt) => elmt + ':not([disabled]):not([aria-hidden])').join(
',',
);

const getFocusableElements = (containerElement: HTMLElement) => {
const elmts = containerElement.querySelectorAll(FOCUSABLE_ELEMENTS_QUERY) as unknown as HTMLElement[];
return [elmts[0], elmts[elmts.length - 1]];
};

const useFocusTrapping = <TElement extends HTMLElement>(ref: React.RefObject<TElement | null>, show: boolean) => {
useEffect(() => {
if (!show || !ref.current) {
return;
}
const containerElement = ref.current;

const handleTabKeyPress = (event: KeyboardEvent) => {
const [firstElement, lastElement] = getFocusableElements(containerElement);
if (event.key === 'Tab') {
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
};

containerElement.addEventListener('keydown', handleTabKeyPress);

return () => {
containerElement.removeEventListener('keydown', handleTabKeyPress);
};
}, [ref, show]);
};

export default useFocusTrapping;
36 changes: 20 additions & 16 deletions src/pages/pegase/home/components/StudyTableDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
*/

import { useState } from 'react';
import StdSimpleTable from '@/components/common/data/stdSimpleTable/StdSimpleTable';
import { StudyDTO } from '@/shared/types/index';
import getStudyTableHeaders from './StudyTableHeaders';
import { addSortColumn, useNewStudyModal } from './StudyTableUtils';
import StudiesPagination from './StudiesPagination';
import { RowSelectionState } from '@tanstack/react-table';
import { useStudyTableDisplay } from './useStudyTableDisplay';
import { RdsButton } from 'rte-design-system-react';

import getStudyTableHeaders from './StudyTableHeaders';
import { addSortColumn } from './StudyTableUtils';
import { StudyStatus } from '@/shared/types/common/StudyStatus.type';
import StdSimpleTable from '@/components/common/data/stdSimpleTable/StdSimpleTable';
import { useStudyTableDisplay } from './useStudyTableDisplay';
import { useTranslation } from 'react-i18next';
import StudyCreationModal from '../../studies/StudyCreationModal';
import {RdsButton} from "rte-design-system-react";

interface StudyTableDisplayProps {
searchStudy: string | undefined;
Expand All @@ -26,18 +27,21 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) =
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [sortedColumn, setSortedColumn] = useState<string | null>('status');
const [isHeaderHovered, setIsHeaderHovered] = useState<boolean>(false);
const { isModalOpen, toggleModal } = useNewStudyModal();
const { t } = useTranslation();
const [selectedStudy, setSelectedStudy] = useState<StudyDTO | null>(null);

const handleSort = (column: string) => {
const newSortOrder = sortByState[column] === 'asc' ? 'desc' : 'asc';
setSortByState({ [column]: newSortOrder });
setSortedColumn(column);
};

const handleHeaderHover = (hovered: boolean) => {
setIsHeaderHovered(hovered);
};

const headers = getStudyTableHeaders();
console.log('Original Headers:', headers);

const sortedHeaders = addSortColumn(
headers,
Expand All @@ -48,8 +52,6 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) =
isHeaderHovered,
);

console.log('Sorted Headers:', sortedHeaders);

const { rows, count, intervalSize, current, setPage } = useStudyTableDisplay({
searchStudy,
projectId,
Expand All @@ -63,6 +65,12 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) =
const isDuplicateActive = selectedStatus === StudyStatus.GENERATED;
const isDeleteActive = selectedStatus === StudyStatus.ERROR || selectedStatus === StudyStatus.IN_PROGRESS;

const handleDuplicate = () => {
const selectedStudy = rows[Number.parseInt(selectedRowId || '-1')];
setSelectedStudy(selectedStudy);
toggleModal();
};

return (
<div>
<div className="flex-1">
Expand All @@ -88,12 +96,7 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) =
<div className="flex gap-2">
{selectedRowId !== undefined ? (
<>
<RdsButton
label="Duplicate"
onClick={() => console.log('duplicate')}
variant="outlined"
disabled={!isDuplicateActive}
/>
<RdsButton label="Duplicate" onClick={handleDuplicate} variant="outlined" disabled={!isDuplicateActive} />
<RdsButton
label="Delete"
onClick={() => console.log('Delete')}
Expand All @@ -103,8 +106,9 @@ const StudyTableDisplay = ({ searchStudy, projectId }: StudyTableDisplayProps) =
/>
</>
) : (
<RdsButton label="NewStudy" onClick={() => console.log('NewStudy')} />
<RdsButton label={t('home.@new_study')} onClick={toggleModal} />
)}
{isModalOpen && <StudyCreationModal isOpen={isModalOpen} onClose={toggleModal} study={selectedStudy} />}
</div>
<StudiesPagination count={count} intervalSize={intervalSize} current={current} onChange={setPage} />
</div>
Expand Down
14 changes: 14 additions & 0 deletions src/pages/pegase/home/components/StudyTableUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { StdIconId } from '@/shared/utils/common/mappings/iconMaps';
import StdIcon from '@common/base/stdIcon/StdIcon';
import { useState } from 'react';

export function addSortColumn(
headers: any[],
Expand Down Expand Up @@ -60,3 +61,16 @@ export function addSortColumn(
};
});
}

export function useNewStudyModal() {
const [isModalOpen, setModalOpen] = useState(false);

const toggleModal = () => {
setModalOpen((prev) => !prev);
};

return {
isModalOpen,
toggleModal,
};
}
51 changes: 51 additions & 0 deletions src/pages/pegase/home/components/studyService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { notifyToast } from '@/shared/notification/notification';

interface StudyData {
name: string;
createdBy: string;
keywords: string[];
project: string;
horizon: string;
trajectoryIds: number[];
}

export const saveStudy = async (studyData: StudyData, toggleModal: () => void) => {
try {
const response = await fetch('http://localhost:8093/v1/study', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(studyData),
});
if (!response.ok) {
const errorText = await response.text();
const errorData = JSON.parse(errorText);
throw new Error(`${errorData.message || errorText}`);
}
notifyToast({
type: 'success',
message: 'Study created successfully',
});
toggleModal();
} catch (error: any) {
notifyToast({
type: 'error',
message: `${error.message}`,
});
}
};
export const fetchSuggestedKeywords = async (query: string): Promise<string[]> => {
const response = await fetch(`http://localhost:8093/v1/study/keywords/search?partialName=${query}`);
if (!response.ok) {
throw new Error('Failed to fetch suggested keywords');
}
const data = await response.json();
return data;
};
2 changes: 1 addition & 1 deletion src/pages/pegase/home/components/useStudyTableDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useEffect, useState } from 'react';
import { StudyDTO } from '@/shared/types/index';
import { getEnvVariables } from '@/envVariables';

const ITEMS_PER_PAGE = 5;
const ITEMS_PER_PAGE = 10;
const BASE_URL = getEnvVariables('VITE_BACK_END_BASE_URL');
const PAGINATION_CURRENT = 0;
const PAGINATION_COUNT = 0;
Expand Down
106 changes: 106 additions & 0 deletions src/pages/pegase/studies/HorizonInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import React, { useState } from 'react';
import { RdsButton, RdsIconId, RdsInputText } from 'rte-design-system-react';

interface YearDropdownProps {
value: string;
onChange: (value: string) => void;
}

const generateYears = (startYear: number, endYear: number): number[] => {
const years = [];
for (let year = startYear; year <= endYear; year++) {
years.push(year);
}
return years;
};

const HorizonInput: React.FC<YearDropdownProps> = ({ value, onChange }) => {
const [isOpen, setIsOpen] = useState(false); // State to control dropdown visibility
const [errorMessage, setErrorMessage] = useState<string>(''); // State for error message

const currentYear = new Date().getFullYear();
const years = generateYears(currentYear, 2050);

const toggleDropdown = () => {
setIsOpen(!isOpen); // Toggle dropdown visibility
};

const handleBlur = () => {
// If the entered value is not in the list, show an error and clear it
if (!years.includes(parseInt(value))) {
setErrorMessage('Veuillez choisir une date valide.');
onChange('');
} else {
setErrorMessage(''); // Clear error if the value is valid
}
};

return (
<div className="relative flex w-[320px] flex-col">
{/* Container for input and button */}
<div className="flex w-full items-center">
{/* Input field */}
<RdsInputText
label="Horizon"
value={value}
onChange={(t) => {
onChange(t || '');
setErrorMessage(''); // Clear error message on input change
}}
onBlur={handleBlur} // Validate input on blur
placeHolder="Select a horizon"
variant="outlined"
/>

{/* Toggle Button */}
<RdsButton
icon={RdsIconId.KeyboardArrowDown}
onClick={toggleDropdown}
size="small"
variant="text"
color="secondary"
/>
</div>

{/* Error Message */}
{errorMessage && <div className="text-red-500 text-sm mt-1">{errorMessage}</div>}

{/* Dropdown list */}
{isOpen && (
<div
className="bg-white max-h-40 absolute z-10 mt-1 w-full overflow-y-auto border border-gray-300"
style={{
backgroundColor: 'white', // Ensure opaque background
maxHeight: '100px',
top: '100%',
left: 0,
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', // Optional: add shadow for better visibility
}}
onMouseDown={(e) => e.preventDefault()} // Prevent dropdown from closing when clicking inside
>
{years.map((year, index) => (
<div
key={index}
className="cursor-pointer px-2 py-1 hover:bg-gray-200"
onClick={() => {
onChange(year.toString());
setIsOpen(false); // Close the dropdown on selection
setErrorMessage(''); // Clear error on valid selection
}}
>
{year}
</div>
))}
</div>
)}
</div>
);
};

export default HorizonInput;
Loading

0 comments on commit fd063b0

Please sign in to comment.