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/ant 2478 create study #28

Merged
merged 9 commits into from
Jan 7, 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
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
Loading