diff --git a/front/src/api/wind/constants.ts b/front/src/api/wind/constants.ts index 529b26e..1c118a7 100644 --- a/front/src/api/wind/constants.ts +++ b/front/src/api/wind/constants.ts @@ -3,4 +3,5 @@ export const WIND_ENDPOINTS = { turbineType: 'api/wind/turbine_type', parks: 'api/wind/parks', park: 'api/wind/park', + parkTurbine: 'api/wind/park_turbine', }; diff --git a/front/src/api/wind/service.ts b/front/src/api/wind/service.ts index d52c09e..af8604d 100644 --- a/front/src/api/wind/service.ts +++ b/front/src/api/wind/service.ts @@ -1,10 +1,11 @@ import { ApiResponse } from '@api/types'; +import { ParkFormValues } from '@components/pages/park-page/types'; import { TurbineTypeFormValues } from '@components/pages/turbine-type-page/types'; import { api } from '../api'; import { WIND_ENDPOINTS } from './constants'; import { Park, ParkTurbine, ParkWithTurbines, TurbineType } from './types'; -import { packTurbineTypes } from './utils'; +import { packPark, packParkTurbine, packTurbineTypes } from './utils'; export const getTurbineTypes = () => { return api.get(WIND_ENDPOINTS.turbines); @@ -63,3 +64,47 @@ export const getParkWithTurbines = async ( error: parkPesponse.error || turbinesResponse.error || null, }; }; + +export const createPark = async (formValues: Partial) => { + const parkPesponse = await api.post( + WIND_ENDPOINTS.park, + packPark(formValues), + ); + await Promise.all( + formValues.turbines?.map((t) => { + return api.post( + WIND_ENDPOINTS.parkTurbine, + packParkTurbine(t, parkPesponse.data.id), + ); + }), + ); + return getParkWithTurbines(String(parkPesponse.data.id)); +}; + +export const updatePark = async ( + formValues: Partial, + id: string, +) => { + const parkPesponse = await api.put( + `${WIND_ENDPOINTS.park}/${id}`, + packPark(formValues), + ); + await Promise.all( + formValues.turbines?.map((t) => { + if (t.new) { + return api.post( + WIND_ENDPOINTS.parkTurbine, + packParkTurbine(t, parkPesponse.data.id), + ); + } + if (t.delete) { + return api.delete(`${WIND_ENDPOINTS.parkTurbine}/${t.id}`); + } + return api.put( + `${WIND_ENDPOINTS.parkTurbine}/${parkPesponse.data.id}/${t.id}`, + packParkTurbine(t, parkPesponse.data.id), + ); + }), + ); + return getParkWithTurbines(id); +}; diff --git a/front/src/api/wind/utils.ts b/front/src/api/wind/utils.ts index ca76e17..5e8f58f 100644 --- a/front/src/api/wind/utils.ts +++ b/front/src/api/wind/utils.ts @@ -1,9 +1,32 @@ +import { + ParkFormTurbine, + ParkFormValues, +} from '@components/pages/park-page/types'; import { TurbineTypeFormValues } from '@components/pages/turbine-type-page/types'; export const packTurbineTypes = (values: Partial) => { return { Name: values.name ?? '', - Height: parseInt(values.height ?? '0'), - BladeLength: parseInt(values.bladeLength ?? '0'), + Height: parseInt(values.height || '0'), + BladeLength: parseInt(values.bladeLength || '0'), + }; +}; + +export const packPark = (values: Partial) => { + return { + Name: values.name ?? '', + CenterLatitude: parseInt(values.centerLatitude || '0'), + CenterLongitude: parseInt(values.centerLongitude || '0'), + }; +}; + +export const packParkTurbine = (turbine: ParkFormTurbine, parkId: number) => { + return { + wind_park_id: parkId, + turbine_id: turbine.id, + x_offset: parseInt(turbine.xOffset || '0'), + y_offset: parseInt(turbine.yOffset || '0'), + angle: parseInt(turbine.angle || '0'), + comment: turbine.comment ?? '', }; }; diff --git a/front/src/components/pages/park-page/component.tsx b/front/src/components/pages/park-page/component.tsx index a247487..1272d75 100644 --- a/front/src/components/pages/park-page/component.tsx +++ b/front/src/components/pages/park-page/component.tsx @@ -1,11 +1,16 @@ -import { getParkWithTurbines, ParkWithTurbines } from '@api/wind'; -import { Button, Heading, NumberInput, TextInput } from '@components/ui'; +import { + createPark, + getParkWithTurbines, + ParkWithTurbines, + updatePark, +} from '@api/wind'; +import { Button, Heading, NumberField, TextInput } from '@components/ui'; +import { ParkTurbines } from '@components/ux'; import { Controller, useForm } from '@utils/form'; -import { useRoute } from '@utils/route'; +import { ROUTES, useRoute } from '@utils/route'; import React, { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; -import { ParkTurbines } from './components'; import styles from './styles.module.scss'; import { ParkFormValues } from './types'; import { unpackPark } from './utils'; @@ -14,6 +19,7 @@ export function ParkPage() { const [park, setPark] = useState(null); const [pending, setPending] = useState(false); const params = useParams(); + const navigate = useNavigate(); const route = useRoute(); const { register, control, getValues, reset } = useForm({}); @@ -35,6 +41,20 @@ export function ParkPage() { fetchPark(); }, [id]); + const handleFormSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setPending(true); + if (isEdit) { + const response = await updatePark(getValues(), id); + setPark(response.data); + reset(unpackPark(response.data)); + } else { + const response = await createPark(getValues()); + navigate(ROUTES.park.path.replace(':id', String(response.data.id))); + } + setPending(false); + }; + const handleReset = () => { if (isEdit) { reset(unpackPark(park)); @@ -44,7 +64,7 @@ export function ParkPage() { }; return ( -
+
{route.title}
@@ -55,13 +75,13 @@ export function ParkPage() { ( - + )} /> ( - + )} />
@@ -76,7 +96,9 @@ export function ParkPage() { } + render={(props) => ( + + )} />
); diff --git a/front/src/components/pages/park-page/components/index.ts b/front/src/components/pages/park-page/components/index.ts deleted file mode 100644 index 83cd964..0000000 --- a/front/src/components/pages/park-page/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './park-turbines'; diff --git a/front/src/components/pages/park-page/components/park-turbines/component.tsx b/front/src/components/pages/park-page/components/park-turbines/component.tsx deleted file mode 100644 index 8dfe1dc..0000000 --- a/front/src/components/pages/park-page/components/park-turbines/component.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { DataGrid } from '@components/ui/data-grid'; -import React from 'react'; - -import { columns } from './constants'; -import { ParkTurbinesProps } from './types'; - -export function ParkTurbines({ value = [] }: ParkTurbinesProps) { - return ( -
-
- String(id)} - selectedItems={[]} - /> -
- ); -} diff --git a/front/src/components/pages/park-page/components/park-turbines/constants.ts b/front/src/components/pages/park-page/components/park-turbines/constants.ts deleted file mode 100644 index 0cc3ce5..0000000 --- a/front/src/components/pages/park-page/components/park-turbines/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DataGridColumnConfig } from '@components/ui/data-grid/types'; -import { ParkTurbine } from 'src/api/wind'; - -export const columns: DataGridColumnConfig[] = [ - { name: 'Id', getText: (t) => String(t.id) }, - { name: 'Name', getText: (t) => t.name }, - { name: 'X', getText: (t) => String(t.xOffset) }, - { name: 'Y', getText: (t) => String(t.yOffset) }, - { name: 'Angle', getText: (t) => String(t.angle) }, - { name: 'Comment', getText: (t) => String(t.comment), flex: '2' }, -]; diff --git a/front/src/components/pages/park-page/components/park-turbines/types.ts b/front/src/components/pages/park-page/components/park-turbines/types.ts deleted file mode 100644 index 3004e17..0000000 --- a/front/src/components/pages/park-page/components/park-turbines/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ParkTurbine } from '@api/wind'; - -export type ParkTurbinesProps = { - value?: ParkTurbine[]; - onChange?: (value: ParkTurbine[]) => void; -}; diff --git a/front/src/components/pages/park-page/types.ts b/front/src/components/pages/park-page/types.ts index 13f5aa4..b366894 100644 --- a/front/src/components/pages/park-page/types.ts +++ b/front/src/components/pages/park-page/types.ts @@ -1,8 +1,17 @@ -import { ParkTurbine } from '@api/wind'; +export type ParkFormTurbine = { + id: number; + name: string; + xOffset: string; + yOffset: string; + angle: string; + comment: string; + new?: boolean; + delete?: boolean; +}; export type ParkFormValues = { name: string; centerLatitude: string; centerLongitude: string; - turbines: ParkTurbine[]; + turbines: ParkFormTurbine[]; }; diff --git a/front/src/components/pages/park-page/utils.ts b/front/src/components/pages/park-page/utils.ts index 2f45d53..41d1260 100644 --- a/front/src/components/pages/park-page/utils.ts +++ b/front/src/components/pages/park-page/utils.ts @@ -7,6 +7,13 @@ export const unpackPark = (park: ParkWithTurbines): ParkFormValues => { name: park.name, centerLatitude: String(park.centerLatitude), centerLongitude: String(park.centerLongitude), - turbines: park.turbines, + turbines: park.turbines.map((t) => ({ + id: t.id, + name: t.name, + xOffset: String(t.xOffset), + yOffset: String(t.yOffset), + angle: String(t.angle), + comment: t.comment, + })), }; }; diff --git a/front/src/components/pages/parks-page/constants.ts b/front/src/components/pages/parks-page/constants.ts index 17d6dea..11ed83b 100644 --- a/front/src/components/pages/parks-page/constants.ts +++ b/front/src/components/pages/parks-page/constants.ts @@ -2,7 +2,7 @@ import { DataGridColumnConfig } from '@components/ui/data-grid/types'; import { Park } from 'src/api/wind'; export const columns: DataGridColumnConfig[] = [ - { name: 'Name', getText: (t) => t.name, flex: '2' }, + { name: 'Name', getText: (t) => t.name, width: 2 }, { name: 'Center Latitude', getText: (t) => String(t.centerLatitude) }, { name: 'Center Longitude', getText: (t) => String(t.centerLongitude) }, ]; diff --git a/front/src/components/pages/parks-page/styles.module.scss b/front/src/components/pages/parks-page/styles.module.scss index 2f566e4..ced7c80 100644 --- a/front/src/components/pages/parks-page/styles.module.scss +++ b/front/src/components/pages/parks-page/styles.module.scss @@ -12,7 +12,6 @@ .actions { display: flex; padding: 10px; - border: 1px solid var(--clr-border-100); border-radius: 15px; background-color: var(--clr-layer-200); box-shadow: 0px 1px 2px var(--clr-shadow-100); diff --git a/front/src/components/pages/turbine-type-page/component.tsx b/front/src/components/pages/turbine-type-page/component.tsx index 30696fe..ed5e3d2 100644 --- a/front/src/components/pages/turbine-type-page/component.tsx +++ b/front/src/components/pages/turbine-type-page/component.tsx @@ -4,7 +4,7 @@ import { getTurbineType, TurbineType, } from '@api/wind'; -import { Button, Heading, NumberInput, TextInput } from '@components/ui'; +import { Button, Heading, NumberField, TextInput } from '@components/ui'; import { Controller, useForm } from '@utils/form'; import { ROUTES, useRoute } from '@utils/route'; import React, { useEffect, useState } from 'react'; @@ -47,6 +47,7 @@ export function TurbineTypePage() { if (isEdit) { const response = await editTurbineTypes(getValues(), id); setTurbineType(response.data); + reset(unpackTurbineType(response.data)); } else { const response = await createTurbineTypes(getValues()); navigate( @@ -76,13 +77,13 @@ export function TurbineTypePage() { ( - + )} /> ( - + )} /> diff --git a/front/src/components/pages/turbine-types-page/constants.ts b/front/src/components/pages/turbine-types-page/constants.ts index 06af2a6..c81bdfe 100644 --- a/front/src/components/pages/turbine-types-page/constants.ts +++ b/front/src/components/pages/turbine-types-page/constants.ts @@ -2,7 +2,7 @@ import { DataGridColumnConfig } from '@components/ui/data-grid/types'; import { TurbineType } from 'src/api/wind'; export const columns: DataGridColumnConfig[] = [ - { name: 'Name', getText: (t) => t.name, flex: '2' }, + { name: 'Name', getText: (t) => t.name, width: 2 }, { name: 'Height', getText: (t) => String(t.height) }, { name: 'Blade length', getText: (t) => String(t.bladeLength) }, ]; diff --git a/front/src/components/pages/turbine-types-page/styles.module.scss b/front/src/components/pages/turbine-types-page/styles.module.scss index 2f566e4..ced7c80 100644 --- a/front/src/components/pages/turbine-types-page/styles.module.scss +++ b/front/src/components/pages/turbine-types-page/styles.module.scss @@ -12,7 +12,6 @@ .actions { display: flex; padding: 10px; - border: 1px solid var(--clr-border-100); border-radius: 15px; background-color: var(--clr-layer-200); box-shadow: 0px 1px 2px var(--clr-shadow-100); diff --git a/front/src/components/ui/data-grid/component.tsx b/front/src/components/ui/data-grid/component.tsx index c40c562..1927b22 100644 --- a/front/src/components/ui/data-grid/component.tsx +++ b/front/src/components/ui/data-grid/component.tsx @@ -15,6 +15,11 @@ export function DataGrid({ }: DataGridProps) { const [allItemsSelected, setAllItemsSelected] = useState(false); + const columnsTemplate = useMemo(() => { + const main = columns.map((c) => `${c.width ?? 1}fr`).join(' '); + return `auto ${main}`; + }, []); + const selectedItemsMap = useMemo(() => { const map: Record = {}; for (let i = 0; i < selectedItems.length; i += 1) { @@ -35,7 +40,7 @@ export function DataGrid({ const handleItemSelect = (key: string, item: T) => { const selected = Boolean(selectedItemsMap[key]); if (!multiselect) { - onItemsSelect(selected ? [] : [item]); + onItemsSelect?.(selected ? [] : [item]); return; } onItemsSelect?.( @@ -52,6 +57,7 @@ export function DataGrid({ columns={columns} allItemsSelected={allItemsSelected} onSelectAllItems={handleSelectAllItems} + columnsTemplate={columnsTemplate} /> {items.map((item) => { const key = String(getItemKey(item)); @@ -62,6 +68,7 @@ export function DataGrid({ selected={Boolean(selectedItemsMap[key])} onSelect={() => handleItemSelect(key, item)} key={getItemKey(item)} + columnsTemplate={columnsTemplate} /> ); })} diff --git a/front/src/components/ui/data-grid/components/DataGridHeader/component.tsx b/front/src/components/ui/data-grid/components/DataGridHeader/component.tsx index b5fdfd7..5baf054 100644 --- a/front/src/components/ui/data-grid/components/DataGridHeader/component.tsx +++ b/front/src/components/ui/data-grid/components/DataGridHeader/component.tsx @@ -14,6 +14,7 @@ export function DataGridHeader({ columns, allItemsSelected, onSelectAllItems, + columnsTemplate, }: DataGridHeaderProps) { const [sort, setSort] = useState({ order: 'asc', column: '' }); @@ -30,7 +31,10 @@ export function DataGridHeader({ }; return ( -
+
({ }); return ( handleSortButtonClick(column.name)} diff --git a/front/src/components/ui/data-grid/components/DataGridHeader/styles.module.scss b/front/src/components/ui/data-grid/components/DataGridHeader/styles.module.scss index 8c37034..9e87119 100644 --- a/front/src/components/ui/data-grid/components/DataGridHeader/styles.module.scss +++ b/front/src/components/ui/data-grid/components/DataGridHeader/styles.module.scss @@ -1,5 +1,5 @@ .header { - display: flex; + display: grid; } .checkboxLabel { @@ -13,7 +13,6 @@ position: relative; display: flex; overflow: hidden; - flex: 1; align-items: center; padding: 10px; border: solid 1px var(--clr-border-100); diff --git a/front/src/components/ui/data-grid/components/DataGridHeader/types.ts b/front/src/components/ui/data-grid/components/DataGridHeader/types.ts index 590d30a..013d3fd 100644 --- a/front/src/components/ui/data-grid/components/DataGridHeader/types.ts +++ b/front/src/components/ui/data-grid/components/DataGridHeader/types.ts @@ -4,4 +4,5 @@ export type DataGridHeaderProps = { columns: DataGridColumnConfig[]; allItemsSelected: boolean; onSelectAllItems: () => void; + columnsTemplate: string; }; diff --git a/front/src/components/ui/data-grid/components/DataGridRow/component.tsx b/front/src/components/ui/data-grid/components/DataGridRow/component.tsx index c55621e..ec35b86 100644 --- a/front/src/components/ui/data-grid/components/DataGridRow/component.tsx +++ b/front/src/components/ui/data-grid/components/DataGridRow/component.tsx @@ -10,20 +10,20 @@ export function DataGridRow({ columns, selected, onSelect, + columnsTemplate, }: DataGridRowProps) { return ( -
+
{columns.map((column) => ( -
+
{column.getText(object)}
))} diff --git a/front/src/components/ui/data-grid/components/DataGridRow/styles.module.scss b/front/src/components/ui/data-grid/components/DataGridRow/styles.module.scss index 43a89f8..3513e15 100644 --- a/front/src/components/ui/data-grid/components/DataGridRow/styles.module.scss +++ b/front/src/components/ui/data-grid/components/DataGridRow/styles.module.scss @@ -1,5 +1,5 @@ .row { - display: flex; + display: grid; } .checkboxLabel { @@ -11,7 +11,6 @@ .cell { display: flex; overflow: hidden; - flex: 1 0 0; align-items: center; padding: 10px; border: solid 1px var(--clr-border-100); diff --git a/front/src/components/ui/data-grid/components/DataGridRow/types.ts b/front/src/components/ui/data-grid/components/DataGridRow/types.ts index f88f239..54fc089 100644 --- a/front/src/components/ui/data-grid/components/DataGridRow/types.ts +++ b/front/src/components/ui/data-grid/components/DataGridRow/types.ts @@ -5,4 +5,5 @@ export type DataGridRowProps = { columns: DataGridColumnConfig[]; selected: boolean; onSelect: () => void; + columnsTemplate: string; }; diff --git a/front/src/components/ui/data-grid/preview.tsx b/front/src/components/ui/data-grid/preview.tsx index 99d8183..710c434 100644 --- a/front/src/components/ui/data-grid/preview.tsx +++ b/front/src/components/ui/data-grid/preview.tsx @@ -24,7 +24,7 @@ export function DataGridPreview() { const columns: DataGridColumnConfig[] = [ { name: 'Name', getText: (cat) => cat.name }, - { name: 'Breed', getText: (cat) => cat.breed, scale: 2 }, + { name: 'Breed', getText: (cat) => cat.breed, width: 2 }, { name: 'Age', getText: (cat) => cat.age }, { name: 'Color', getText: (cat) => cat.color }, ]; diff --git a/front/src/components/ui/data-grid/types.ts b/front/src/components/ui/data-grid/types.ts index 253debf..736918e 100644 --- a/front/src/components/ui/data-grid/types.ts +++ b/front/src/components/ui/data-grid/types.ts @@ -4,7 +4,7 @@ export type DataGridColumnConfig = { name: string; getText: (object: T) => string; sortable?: boolean; - flex?: string; + width?: number; }; export type DataGridSort = { diff --git a/front/src/components/ui/index.ts b/front/src/components/ui/index.ts index 83cf09b..9831603 100644 --- a/front/src/components/ui/index.ts +++ b/front/src/components/ui/index.ts @@ -3,13 +3,14 @@ export { Button } from './button'; export { Checkbox } from './checkbox'; export { CheckboxGroup } from './checkbox-group'; export { DateInput } from './date-input'; +export { Dialog } from './dialog'; export { FileUploader } from './file-uploader'; export { Heading } from './heading'; export { IconButton } from './icon-button'; export { ImageFileManager } from './image-file-manager'; export { LinkButton } from './link-button'; export { Menu } from './menu'; -export { Dialog } from './dialog'; +export { NumberField } from './number-field'; export { NumberInput } from './number-input'; export { Overlay } from './overlay'; export { Pagination } from './pagination'; diff --git a/front/src/components/ui/number-field/component.tsx b/front/src/components/ui/number-field/component.tsx new file mode 100644 index 0000000..e9adcb3 --- /dev/null +++ b/front/src/components/ui/number-field/component.tsx @@ -0,0 +1,28 @@ +import React, { ForwardedRef, forwardRef } from 'react'; + +import { Label, LabelProps } from '../label'; +import { NumberInput } from '../number-input'; +import { NumberFieldProps } from './types'; + +function NumberFieldInner( + { scale, label = {}, required, ...props }: Omit, + ref: ForwardedRef, +) { + const labelProps: LabelProps = { + ...label, + required: { value: required, ...label.required }, + }; + return ( + + ); +} + +export const NumberField = forwardRef(NumberFieldInner); diff --git a/front/src/components/pages/park-page/components/park-turbines/index.ts b/front/src/components/ui/number-field/index.tsx similarity index 100% rename from front/src/components/pages/park-page/components/park-turbines/index.ts rename to front/src/components/ui/number-field/index.tsx diff --git a/front/src/components/ui/number-field/types.ts b/front/src/components/ui/number-field/types.ts new file mode 100644 index 0000000..0050137 --- /dev/null +++ b/front/src/components/ui/number-field/types.ts @@ -0,0 +1,6 @@ +import { LabelProps } from '../label/types'; +import { NumberInputProps } from '../number-input/types'; + +export type NumberFieldProps = { + label?: LabelProps; +} & NumberInputProps; diff --git a/front/src/components/ui/number-input/component.tsx b/front/src/components/ui/number-input/component.tsx index eef6896..c47bbdc 100644 --- a/front/src/components/ui/number-input/component.tsx +++ b/front/src/components/ui/number-input/component.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { TextInput } from '../text-input'; +import { Input } from '../input'; import { NumberInputProps } from './types'; export function NumberInput({ @@ -34,5 +34,5 @@ export function NumberInput({ onChange?.(num); }; - return ; + return ; } diff --git a/front/src/components/ux/index.ts b/front/src/components/ux/index.ts index da680f3..dc77ef0 100644 --- a/front/src/components/ux/index.ts +++ b/front/src/components/ux/index.ts @@ -1,5 +1,5 @@ export { Header } from './header'; +export { ParkTurbineTable } from './park-turbine-table'; +export { ParkTurbines } from './park-turbines'; export { Sidebar } from './sidebar'; -export { SignInForm } from './sign-in-form'; -export { SignUpForm } from './sign-up-form'; export { ThemeSelect } from './theme-select'; diff --git a/front/src/components/ux/park-turbine-table/component.tsx b/front/src/components/ux/park-turbine-table/component.tsx new file mode 100644 index 0000000..df07ef1 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/component.tsx @@ -0,0 +1,35 @@ +import { ParkFormTurbine } from '@components/pages/park-page/types'; +import React from 'react'; + +import { ParkTurbineTableHeader, ParkTurbineTableRow } from './components'; +import { ParkTurbineTableProps } from './types'; + +export function ParkTurbineTable({ + turbines, + onChange, + selectedIDs, + onSelect, +}: ParkTurbineTableProps) { + const handleRowChange = (turbine: ParkFormTurbine, index: number) => { + onChange(turbines.with(index, turbine)); + }; + + const handleSelect = (turbineId: number) => { + onSelect({ ...selectedIDs, [turbineId]: !selectedIDs[turbineId] }); + }; + + return ( +
+ + {turbines.map((turbine, index) => ( + handleRowChange(t, index)} + selected={selectedIDs[turbine.id] ?? false} + onSelect={() => handleSelect(turbine.id)} + key={turbine.id} + /> + ))} +
+ ); +} diff --git a/front/src/components/ux/park-turbine-table/components/index.ts b/front/src/components/ux/park-turbine-table/components/index.ts new file mode 100644 index 0000000..d85b00d --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/index.ts @@ -0,0 +1,2 @@ +export * from './park-turbine-table-header'; +export * from './park-turbine-table-row'; diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/component.tsx b/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/component.tsx new file mode 100644 index 0000000..bf27c0e --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/component.tsx @@ -0,0 +1,35 @@ +import { Checkbox } from '@components/ui/checkbox'; +import { Span } from '@components/ui/span'; +import React from 'react'; + +import styles from './styles.module.scss'; + +export function ParkTurbineTableHeader() { + return ( +
+ {}} + label={{ className: styles.checkboxLabel }} + /> + + Id + + + Name + + + X + + + Y + + + Angle + + + Comment + +
+ ); +} diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/index.ts b/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/index.ts new file mode 100644 index 0000000..bb82484 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/index.ts @@ -0,0 +1 @@ +export * from './component'; diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/styles.module.scss b/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/styles.module.scss new file mode 100644 index 0000000..f8f5a93 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-header/styles.module.scss @@ -0,0 +1,26 @@ +.header { + display: grid; + grid-template-columns: auto 1fr 1fr 1fr 1fr 1fr 3fr; +} + +.checkboxLabel { + padding: 10px; + border: solid 1px var(--clr-border-100); + background-color: var(--clr-layer-300); + border-top-left-radius: 10px; +} + +.cell { + position: relative; + display: flex; + overflow: hidden; + align-items: center; + padding: 10px; + border: solid 1px var(--clr-border-100); + background-color: var(--clr-layer-300); + transition: all var(--td-100) ease-in-out; + + &:last-of-type { + border-top-right-radius: 10px; + } +} diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/component.tsx b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/component.tsx new file mode 100644 index 0000000..9c41d82 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/component.tsx @@ -0,0 +1,52 @@ +import { NumberInput } from '@components/ui'; +import { Checkbox } from '@components/ui/checkbox'; +import { Input } from '@components/ui/input'; +import React from 'react'; + +import styles from './styles.module.scss'; +import { ParkTurbineTableRowProps } from './types'; + +export function ParkTurbineTableRow({ + turbine, + onChange, + selected, + onSelect, +}: ParkTurbineTableRowProps) { + return ( +
+ + + + onChange({ ...turbine, xOffset: value })} + /> + onChange({ ...turbine, yOffset: value })} + /> + onChange({ ...turbine, angle: value })} + /> + + onChange({ ...turbine, comment: event.target.value }) + } + /> +
+ ); +} diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/index.ts b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/index.ts new file mode 100644 index 0000000..bb82484 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/index.ts @@ -0,0 +1 @@ +export * from './component'; diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/styles.module.scss b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/styles.module.scss new file mode 100644 index 0000000..411fb14 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/styles.module.scss @@ -0,0 +1,16 @@ +.row { + display: grid; + grid-template-columns: auto 1fr 1fr 1fr 1fr 1fr 3fr; + + .cell { + border: solid 1px var(--clr-border-100); + border-radius: 0; + background-color: var(--clr-layer-200); + } +} + +.checkboxLabel { + padding: 10px; + border: solid 1px var(--clr-border-100); + background-color: var(--clr-layer-200); +} diff --git a/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/types.ts b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/types.ts new file mode 100644 index 0000000..ef79d99 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/components/park-turbine-table-row/types.ts @@ -0,0 +1,8 @@ +import { ParkFormTurbine } from '@components/pages/park-page/types'; + +export type ParkTurbineTableRowProps = { + turbine: ParkFormTurbine; + onChange: (turbine: ParkFormTurbine) => void; + selected: boolean; + onSelect: () => void; +}; diff --git a/front/src/components/ux/park-turbine-table/index.tsx b/front/src/components/ux/park-turbine-table/index.tsx new file mode 100644 index 0000000..bb82484 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/index.tsx @@ -0,0 +1 @@ +export * from './component'; diff --git a/front/src/components/ux/park-turbine-table/types.ts b/front/src/components/ux/park-turbine-table/types.ts new file mode 100644 index 0000000..5124ab2 --- /dev/null +++ b/front/src/components/ux/park-turbine-table/types.ts @@ -0,0 +1,8 @@ +import { ParkFormTurbine } from '@components/pages/park-page/types'; + +export type ParkTurbineTableProps = { + turbines: ParkFormTurbine[]; + onChange: (turbines: ParkFormTurbine[]) => void; + selectedIDs: Record; + onSelect: (selected: Record) => void; +}; diff --git a/front/src/components/ux/park-turbines/component.tsx b/front/src/components/ux/park-turbines/component.tsx new file mode 100644 index 0000000..1e597ec --- /dev/null +++ b/front/src/components/ux/park-turbines/component.tsx @@ -0,0 +1,87 @@ +import { getTurbineTypes, TurbineType } from '@api/wind'; +import { ParkFormTurbine } from '@components/pages/park-page/types'; +import { Autocomplete, Button } from '@components/ui'; +import React, { useEffect, useState } from 'react'; + +import { ParkTurbineTable } from '../park-turbine-table'; +import styles from './styles.module.scss'; +import { ParkTurbinesProps } from './types'; + +export function ParkTurbines({ + savedTurbines, + value = [], + onChange, +}: ParkTurbinesProps) { + const [turbineTypes, setTurbineTypes] = useState([]); + const [turbineType, setTurbineType] = useState(null); + const [selectedIDs, setSelectedIDs] = useState>({}); + + const fetchTurbineTypes = async () => { + const res = await getTurbineTypes(); + setTurbineTypes(res.data ?? []); + }; + + useEffect(() => { + fetchTurbineTypes(); + }, []); + + const handleAddButtonClick = () => { + if ( + savedTurbines.find((t) => t.id === turbineType.id) || + value.find((t) => t.id === turbineType.id) + ) { + return; + } + onChange([ + ...value, + { + id: turbineType.id, + name: turbineType.name, + xOffset: '', + yOffset: '', + angle: '', + comment: '', + new: true, + }, + ]); + }; + + const handleDeleteButtonClick = () => { + const newValue: ParkFormTurbine[] = []; + value.forEach((turbine) => { + if (!selectedIDs[turbine.id]) { + newValue.push(turbine); + return; + } + if (savedTurbines.find((t) => t.id === turbine.id)) { + newValue.push({ ...turbine, delete: true }); + } + }); + onChange(newValue); + setSelectedIDs({}); + }; + + return ( +
+
+ id} + getOptionLabel={({ name }) => name} + onChange={(t) => setTurbineType(t)} + value={turbineType} + /> + + +
+ !t.delete)} + onChange={onChange} + selectedIDs={selectedIDs} + onSelect={setSelectedIDs} + /> +
+ ); +} diff --git a/front/src/components/ux/park-turbines/index.ts b/front/src/components/ux/park-turbines/index.ts new file mode 100644 index 0000000..bb82484 --- /dev/null +++ b/front/src/components/ux/park-turbines/index.ts @@ -0,0 +1 @@ +export * from './component'; diff --git a/front/src/components/ux/park-turbines/styles.module.scss b/front/src/components/ux/park-turbines/styles.module.scss new file mode 100644 index 0000000..45504fa --- /dev/null +++ b/front/src/components/ux/park-turbines/styles.module.scss @@ -0,0 +1,14 @@ +.parkTurbines { + display: grid; + gap: 10px; + grid-template-rows: auto 1fr; +} + +.actions { + display: flex; + padding: 10px; + border-radius: 15px; + background-color: var(--clr-layer-200); + box-shadow: 0px 1px 2px var(--clr-shadow-100); + gap: 10px; +} diff --git a/front/src/components/ux/park-turbines/types.ts b/front/src/components/ux/park-turbines/types.ts new file mode 100644 index 0000000..7c01e1b --- /dev/null +++ b/front/src/components/ux/park-turbines/types.ts @@ -0,0 +1,8 @@ +import { ParkTurbine } from '@api/wind'; +import { ParkFormTurbine } from '@components/pages/park-page/types'; + +export type ParkTurbinesProps = { + savedTurbines: ParkTurbine[]; + value?: ParkFormTurbine[]; + onChange?: (value: ParkFormTurbine[]) => void; +}; diff --git a/front/src/components/ux/sign-in-form/component.tsx b/front/src/components/ux/sign-in-form/component.tsx deleted file mode 100644 index 5299ab1..0000000 --- a/front/src/components/ux/sign-in-form/component.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - Button, - Heading, - LinkButton, - PasswordInput, - TextInput, -} from '@components/ui'; -import { useForm } from '@utils/form'; -import { ROUTES } from '@utils/route'; -import clsx from 'clsx'; -import React from 'react'; - -import styles from './styles.module.scss'; -import { SignInFormProps, SignInFormStore } from './types'; - -export function SignInForm({ className, ...props }: SignInFormProps) { - const { register, getValues } = useForm({}); - const classNames = clsx(className, styles.form); - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - console.log(getValues()); - }; - - return ( -
- - Sign in - -
- - -
-
- - Not a member? -
-
- ); -} diff --git a/front/src/components/ux/sign-in-form/index.ts b/front/src/components/ux/sign-in-form/index.ts deleted file mode 100644 index fba6544..0000000 --- a/front/src/components/ux/sign-in-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SignInForm } from './component'; diff --git a/front/src/components/ux/sign-in-form/styles.module.scss b/front/src/components/ux/sign-in-form/styles.module.scss deleted file mode 100644 index b996084..0000000 --- a/front/src/components/ux/sign-in-form/styles.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -.form { - display: grid; - padding: 40px 20px 20px; - border-radius: 15px; - background-color: var(--clr-layer-200); - box-shadow: 0px 1px 2px var(--clr-shadow-100); - gap: 30px; - - & > * { - width: 100%; - } -} - -.heading { - text-align: center; -} - -.inputBox { - display: grid; - gap: 20px; -} - -.buttonBox { - display: grid; - gap: 10px; -} diff --git a/front/src/components/ux/sign-in-form/types.ts b/front/src/components/ux/sign-in-form/types.ts deleted file mode 100644 index caa4093..0000000 --- a/front/src/components/ux/sign-in-form/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ComponentProps } from 'react'; - -export type SignInFormStore = { - email: string; - password: string; -}; - -export type SignInFormProps = {} & ComponentProps<'form'>; diff --git a/front/src/components/ux/sign-up-form/component.tsx b/front/src/components/ux/sign-up-form/component.tsx deleted file mode 100644 index caeb375..0000000 --- a/front/src/components/ux/sign-up-form/component.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - Button, - Heading, - LinkButton, - PasswordInput, - TextInput, -} from '@components/ui'; -import { useForm } from '@utils/form'; -import { ROUTES } from '@utils/route'; -import clsx from 'clsx'; -import React from 'react'; - -import styles from './styles.module.scss'; -import { SignUpFormProps, SignUpFormStore } from './types'; - -export function SignUpForm({ className, ...props }: SignUpFormProps) { - const { register, getValues } = useForm({}); - const classNames = clsx(className, styles.form); - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - console.log(getValues()); - }; - - return ( -
- - Sign up - -
- - -
-
- - Already a member? -
-
- ); -} diff --git a/front/src/components/ux/sign-up-form/index.ts b/front/src/components/ux/sign-up-form/index.ts deleted file mode 100644 index 455d405..0000000 --- a/front/src/components/ux/sign-up-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SignUpForm } from './component'; diff --git a/front/src/components/ux/sign-up-form/styles.module.scss b/front/src/components/ux/sign-up-form/styles.module.scss deleted file mode 100644 index b996084..0000000 --- a/front/src/components/ux/sign-up-form/styles.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -.form { - display: grid; - padding: 40px 20px 20px; - border-radius: 15px; - background-color: var(--clr-layer-200); - box-shadow: 0px 1px 2px var(--clr-shadow-100); - gap: 30px; - - & > * { - width: 100%; - } -} - -.heading { - text-align: center; -} - -.inputBox { - display: grid; - gap: 20px; -} - -.buttonBox { - display: grid; - gap: 10px; -} diff --git a/front/src/components/ux/sign-up-form/types.ts b/front/src/components/ux/sign-up-form/types.ts deleted file mode 100644 index e089c14..0000000 --- a/front/src/components/ux/sign-up-form/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ComponentProps } from 'react'; - -export type SignUpFormStore = { - email: string; - password: string; - passwordRepeat: string; -}; - -export type SignUpFormProps = {} & ComponentProps<'form'>; diff --git a/server/src/data/repository.py b/server/src/data/repository.py index 1de7dfd..edcadc5 100644 --- a/server/src/data/repository.py +++ b/server/src/data/repository.py @@ -143,12 +143,12 @@ class WindParkTurbineRepository: return db_park_turbine @staticmethod - def get(db: Session, park_turbine_id: int): - return db.query(WindParkTurbine).filter(WindParkTurbine.turbine_id == park_turbine_id).first() + def get(db: Session, park_id: int, turbine_id: int): + return db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, WindParkTurbine.turbine_id == turbine_id).first() @staticmethod - def update(db: Session, park_turbine_id: int, park_turbine: WindParkTurbineCreate): - db_park_turbine = db.query(WindParkTurbine).filter(WindParkTurbine.turbine_id == park_turbine_id).first() + def update(db: Session, park_id: int, turbine_id: int, park_turbine: WindParkTurbineCreate): + db_park_turbine = db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, WindParkTurbine.turbine_id == turbine_id).first() if db_park_turbine: for key, value in park_turbine.dict().items(): setattr(db_park_turbine, key, value) diff --git a/server/src/routers/wind_park_router.py b/server/src/routers/wind_park_router.py index 8cb60f4..07a40aa 100644 --- a/server/src/routers/wind_park_router.py +++ b/server/src/routers/wind_park_router.py @@ -77,17 +77,17 @@ async def create_park_turbine(park_turbine: WindParkTurbineCreate, db: Session = return WindParkTurbineRepository.create(db=db, park_turbine=park_turbine) -@router.get("/park_turbine/{park_turbine_id}", response_model=WindParkTurbineResponse) -async def read_park_turbine(park_turbine_id: int, db: Session = Depends(get_db)): - park_turbine = WindParkTurbineRepository.get(db=db, park_turbine_id=park_turbine_id) +@router.get("/park_turbine/{park_id}/{turbine_id}", response_model=WindParkTurbineResponse) +async def read_park_turbine(park_id: int, turbine_id: int, db: Session = Depends(get_db)): + park_turbine = WindParkTurbineRepository.get(db=db, park_id=park_id, turbine_id=turbine_id) if park_turbine is None: raise HTTPException(status_code=404, detail="Park Turbine not found") return park_turbine -@router.put("/park_turbine/{park_turbine_id}", response_model=WindParkTurbineResponse) -async def update_park_turbine(park_turbine_id: int, park_turbine: WindParkTurbineCreate, db: Session = Depends(get_db)): - updated_park_turbine = WindParkTurbineRepository.update(db=db, park_turbine_id=park_turbine_id, +@router.put("/park_turbine/{park_id}/{turbine_id}", response_model=WindParkTurbineResponse) +async def update_park_turbine(park_id: int, turbine_id: int, park_turbine: WindParkTurbineCreate, db: Session = Depends(get_db)): + updated_park_turbine = WindParkTurbineRepository.update(db=db, park_id=park_id, turbine_id=turbine_id, park_turbine=park_turbine) if updated_park_turbine is None: raise HTTPException(status_code=404, detail="Park Turbine not found")