added more fields to a bazaar project

Signed-off-by: Lykke Axlin <lykkeaxlin@hotmail.com>

Co-authored-by: klaraab <klarabroman@live.se>
This commit is contained in:
Lykke Axlin
2021-11-09 15:08:22 +01:00
parent fc84dda0b6
commit 57c24468ba
20 changed files with 446 additions and 106 deletions
@@ -16,11 +16,11 @@
import React, { useState, useEffect } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { SubmitHandler } from 'react-hook-form';
import { UseFormReset, UseFormGetValues } from 'react-hook-form';
import { useApi } from '@backstage/core-plugin-api';
import { ProjectDialog } from '../ProjectDialog';
import { ProjectSelector } from '../ProjectSelector';
import { BazaarProject, FormValues, Status } from '../../types';
import { BazaarProject, FormValues, Size, Status } from '../../types';
import { bazaarApiRef } from '../../api';
type Props = {
@@ -52,6 +52,10 @@ export const AddProjectDialog = ({
community: '',
announcement: '',
status: 'proposed' as Status,
size: 'medium' as Size,
responsible: '',
startDate: null,
endDate: null,
};
const handleListItemClick = (entity: Entity) => {
@@ -63,9 +67,9 @@ export const AddProjectDialog = ({
handleClose();
};
const handleSave: SubmitHandler<FormValues> = async (
getValues: any,
reset: any,
const handleSave: any = async (
getValues: UseFormGetValues<FormValues>,
reset: UseFormReset<FormValues>,
) => {
const formValues = getValues();
@@ -77,6 +81,10 @@ export const AddProjectDialog = ({
status: formValues.status,
community: formValues.community,
membersCount: 0,
size: formValues.size,
startDate: formValues.startDate ?? null,
endDate: formValues.endDate ?? null,
responsible: formValues.responsible,
} as BazaarProject);
fetchBazaarProjects();
@@ -0,0 +1,72 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import FormControl from '@material-ui/core/FormControl';
import { Controller, Control, UseFormSetValue } from 'react-hook-form';
import { FormValues } from '../../types';
import {
KeyboardDatePicker,
MuiPickersUtilsProvider,
} from '@material-ui/pickers';
import LuxonUtils from '@date-io/luxon';
import { IconButton } from '@material-ui/core';
import ClearIcon from '@material-ui/icons/Clear';
type Props = {
name: 'startDate' | 'endDate';
control: Control<FormValues, object>;
setValue: UseFormSetValue<FormValues>;
};
export const DateSelector = ({ name, control, setValue }: Props) => {
const label = `${
name.charAt(0).toLocaleUpperCase('en-US') + name.slice(1, name.indexOf('D'))
} date`;
return (
<Controller
name={name}
control={control}
render={({ field }) => (
<FormControl>
<MuiPickersUtilsProvider utils={LuxonUtils}>
<KeyboardDatePicker
disablePast
disableToolbar
format="dd-MM-yyyy"
label={label}
value={field.value}
onChange={date => {
setValue(name, date?.toISO());
}}
InputProps={{
endAdornment: (
<IconButton onClick={() => setValue(name, null)}>
<ClearIcon />
</IconButton>
),
}}
InputAdornmentProps={{
position: 'start',
}}
/>
</MuiPickersUtilsProvider>
</FormControl>
)}
/>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DateSelector } from './DateSelector';
@@ -0,0 +1,67 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Control, UseFormSetValue } from 'react-hook-form';
import { FormValues } from '../../types';
import { Typography } from '@material-ui/core';
import { DateSelector } from '../DateSelector/DateSelector';
type Props = {
control: Control<FormValues, object>;
setValue: UseFormSetValue<FormValues>;
};
export const DoubleDateSelector = ({ control, setValue }: Props) => {
return (
<div
style={{
marginTop: '0.25rem',
textAlign: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<div
style={{
float: 'left',
}}
>
<DateSelector name="startDate" control={control} setValue={setValue} />
</div>
<Typography
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: '1.5rem',
margin: '0 1rem',
}}
>
-
</Typography>
<div
style={{
float: 'right',
}}
>
<DateSelector name="endDate" control={control} setValue={setValue} />
</div>
</div>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DoubleDateSelector } from './DoubleDateSelector';
@@ -18,8 +18,9 @@ import React, { useState, useEffect } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { ProjectDialog } from '../ProjectDialog';
import { BazaarProject, FormValues } from '../../types';
import { BazaarProject, FormValues, Size, Status } from '../../types';
import { bazaarApiRef } from '../../api';
import { UseFormGetValues } from 'react-hook-form';
type Props = {
entity: Entity;
@@ -41,6 +42,10 @@ export const EditProjectDialog = ({
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
size: bazaarProject.size,
startDate: bazaarProject?.startDate ?? null,
endDate: bazaarProject?.endDate ?? null,
responsible: bazaarProject.responsible,
});
const bazaarApi = useApi(bazaarApiRef);
@@ -50,19 +55,27 @@ export const EditProjectDialog = ({
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
size: bazaarProject.size,
startDate: bazaarProject?.startDate ?? null,
endDate: bazaarProject?.endDate ?? null,
responsible: bazaarProject.responsible,
});
}, [bazaarProject]);
const handleSave: any = async (getValues: any, _: any) => {
const handleSave: any = async (getValues: UseFormGetValues<FormValues>) => {
const formValues = getValues();
const updateResponse = await bazaarApi.updateMetadata({
name: entity.metadata.name,
entityRef: stringifyEntityRef(entity),
announcement: formValues.announcement,
status: formValues.status,
status: formValues.status as Status,
community: formValues.community,
membersCount: bazaarProject.membersCount,
size: formValues.size as Size,
startDate: formValues?.startDate ?? null,
endDate: formValues?.endDate ?? null,
responsible: formValues.responsible,
});
if (updateResponse.status === 'ok') fetchBazaarProject();
@@ -121,6 +121,10 @@ export const EntityBazaarInfoCard = () => {
status: metadata.status,
updatedAt: metadata.updated_at,
membersCount: metadata.members_count,
size: metadata.size,
startDate: metadata.start_date,
endDate: metadata.end_date,
responsible: metadata.responsible,
} as BazaarProject;
}
}
@@ -259,7 +263,7 @@ export const EntityBazaarInfoCard = () => {
</MenuList>
</Popover>
<Grid container>
<Grid item xs={12}>
<Grid item xs={10}>
<AboutField label="Announcement">
{bazaarProject?.value?.announcement
? bazaarProject?.value?.announcement
@@ -278,17 +282,11 @@ export const EntityBazaarInfoCard = () => {
</AboutField>
</Grid>
<Grid item xs={6}>
<AboutField label="Status">
<StatusTag status={bazaarProject?.value?.status || 'proposed'} />
</AboutField>
</Grid>
<Grid item xs={6}>
<Grid style={{ marginRight: '0rem' }}>
{' '}
<AboutField label="Latest members">
{members?.value?.length ? (
members.value.slice(0, 3).map((member: Member) => {
members.value.slice(0, 7).map((member: Member) => {
return (
<div key={member.userId}>
<Avatar
@@ -299,6 +297,7 @@ export const EntityBazaarInfoCard = () => {
fontSize: '8px',
float: 'left',
marginRight: '0.3rem',
marginBottom: '0.25rem',
}}
picture={member.picture}
/>
@@ -317,6 +316,44 @@ export const EntityBazaarInfoCard = () => {
)}
</AboutField>
</Grid>
<Grid item xs={2}>
<AboutField label="Status">
<StatusTag status={bazaarProject?.value?.status || 'proposed'} />
</AboutField>
</Grid>
<Grid item xs={2}>
<AboutField label="size">
<Typography variant="body2">
{bazaarProject?.value?.size}
</Typography>
</AboutField>
</Grid>
<Grid item xs={2}>
<AboutField label="Start date">
<Typography variant="body2">
{bazaarProject?.value?.startDate?.substring(0, 10) || ''}
</Typography>
</AboutField>
</Grid>
<Grid item xs={2}>
<AboutField label="End date">
<Typography variant="body2">
{bazaarProject?.value?.endDate?.substring(0, 10) || ''}
</Typography>
</AboutField>
</Grid>
<Grid item xs={2}>
<AboutField label="Responsible">
<Typography variant="body2">
{bazaarProject?.value?.responsible || ''}
</Typography>
</AboutField>
</Grid>
</Grid>
</CardContent>
</Card>
@@ -19,13 +19,18 @@ import { Controller, Control, FieldError } from 'react-hook-form';
import { TextField } from '@material-ui/core';
import { FormValues } from '../../types';
type Rules = {
required: boolean;
pattern?: any;
};
type Props = {
inputType: 'announcement' | 'community';
inputType: 'announcement' | 'community' | 'responsible';
error?: FieldError | undefined;
control: Control<FormValues, object>;
helperText?: string;
placeholder?: string;
rules?: Object;
rules?: Rules | undefined;
};
export const InputField = ({
@@ -47,6 +52,7 @@ export const InputField = ({
render={({ field }) => (
<TextField
{...field}
required={rules?.required}
margin="dense"
multiline
id="title"
@@ -25,7 +25,7 @@ import { FormValues } from '../../types';
type Props = {
options: string[];
control: Control<FormValues, object>;
name: 'announcement' | 'status';
name: 'status' | 'size';
error?: FieldError | undefined;
};
@@ -42,14 +42,17 @@ export const InputSelector = ({ name, options, control, error }: Props) => {
render={({ field }) => (
<FormControl fullWidth>
<InputLabel
required
htmlFor="demo-simple-select-outlined"
id="demo-simple-select-outlined-label"
style={{
marginTop: '0.25rem',
}}
>
{label}
</InputLabel>
<Select
{...field}
required
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
label={label}
@@ -26,10 +26,16 @@ import MuiDialogContent from '@material-ui/core/DialogContent';
import MuiDialogActions from '@material-ui/core/DialogActions';
import CloseIcon from '@material-ui/icons/Close';
import { Button, Dialog, Typography, IconButton } from '@material-ui/core';
import { useForm, SubmitHandler } from 'react-hook-form';
import {
useForm,
SubmitHandler,
UseFormReset,
UseFormGetValues,
} from 'react-hook-form';
import { InputField } from '../InputField/InputField';
import { InputSelector } from '../InputSelector/InputSelector';
import { FormValues } from '../../types';
import { DoubleDateSelector } from '../DoubleDateSelector/DoubleDateSelector';
const styles = (theme: Theme) =>
createStyles({
@@ -87,7 +93,10 @@ const DialogActions = withStyles((theme: Theme) => ({
}))(MuiDialogActions);
type Props = {
handleSave: (getValues: any, reset: any) => SubmitHandler<FormValues>;
handleSave: (
getValues: UseFormGetValues<FormValues>,
reset: UseFormReset<FormValues>,
) => SubmitHandler<FormValues>;
isAddForm: boolean;
title: string;
defaultValues: FormValues;
@@ -111,6 +120,7 @@ export const ProjectDialog = ({
control,
getValues,
formState: { errors },
setValue,
} = useForm<FormValues>({
mode: 'onChange',
defaultValues: defaultValues,
@@ -137,7 +147,6 @@ export const ProjectDialog = ({
<DialogTitle id="customized-dialog-title" onClose={handleCloseAndClear}>
{title}
</DialogTitle>
<DialogContent dividers>
{isAddForm && projectSelector}
@@ -152,23 +161,41 @@ export const ProjectDialog = ({
placeholder="Describe who you are and what skills you are looking for"
/>
<InputSelector
control={control}
name="status"
options={['proposed', 'ongoing']}
/>
<InputSelector
control={control}
name="size"
options={['small', 'medium', 'large']}
/>
<DoubleDateSelector setValue={setValue} control={control} />
<InputField
error={errors.responsible}
control={control}
rules={{
required: false,
}}
inputType="responsible"
placeholder="Contact person of the project"
/>
<InputField
error={errors.community}
control={control}
rules={{
required: false,
pattern: RegExp('^(https?)://[^s$.?#].[^s]*$'),
pattern: RegExp('^(https?)://'),
}}
inputType="community"
helperText="please enter a link starting with http/https"
placeholder="Community link to e.g. Teams or Discord"
/>
<InputSelector
control={control}
name="status"
options={['proposed', 'ongoing']}
/>
</DialogContent>
<DialogActions>
@@ -17,8 +17,7 @@
import React, { useState } from 'react';
import { Content } from '@backstage/core-components';
import { ProjectCard } from '../ProjectCard/ProjectCard';
import { makeStyles, Grid } from '@material-ui/core';
import Pagination from '@material-ui/lab/Pagination';
import { makeStyles, Grid, TablePagination } from '@material-ui/core';
import { BazaarProject } from '../../types';
type Props = {
@@ -42,12 +41,18 @@ const useStyles = makeStyles({
export const ProjectPreview = ({ bazaarProjects, sortingMethod }: Props) => {
const classes = useStyles();
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;
const pageCount = Math.ceil(bazaarProjects.length / pageSize);
const [page, setPage] = useState(1);
const [rowsPerPage, setRowsPerPage] = useState(10);
const handlePageClick = (_: any, pageIndex: number) => {
setCurrentPage(pageIndex);
const handleChangePage = (_: any, newPage: number) => {
setPage(newPage + 1);
};
const handleChangeRowsPerPage = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(1);
};
if (!bazaarProjects.length) {
@@ -72,37 +77,31 @@ export const ProjectPreview = ({ bazaarProjects, sortingMethod }: Props) => {
<Content className={classes.content} noPadding>
<Grid wrap="wrap" container spacing={3}>
{bazaarProjects
.slice(pageSize * (currentPage - 1), pageSize * currentPage)
.map((bazaarProject: BazaarProject) => {
const entityRef = bazaarProject.entityRef;
.slice((page - 1) * rowsPerPage, rowsPerPage * page)
.map((bazaarProject: BazaarProject, i: number) => {
return (
<Grid
key={(entityRef as string) || ''}
className={classes.item}
item
xs={3}
>
<ProjectCard
bazaarProject={bazaarProject}
key={Math.random()}
/>
<Grid key={i} className={classes.item} item xs={3}>
<ProjectCard bazaarProject={bazaarProject} key={i} />
</Grid>
);
})}
</Grid>
<Pagination
showFirstButton
showLastButton
siblingCount={2}
<TablePagination
count={bazaarProjects?.length}
page={page - 1}
onPageChange={handleChangePage}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={handleChangeRowsPerPage}
backIconButtonProps={{ disabled: page === 1 }}
nextIconButtonProps={{
disabled: rowsPerPage * page >= bazaarProjects.length,
}}
style={{
marginTop: '1rem',
marginLeft: 'auto',
marginRight: '0',
}}
count={pageCount}
page={currentPage}
onChange={handlePageClick}
/>
</Content>
);
@@ -45,7 +45,7 @@ export const ProjectSelector = ({
isFormInvalid && value === '' ? 'Please select a project' : ''
}
{...params}
label="Select a project"
label="Select a project *"
/>
)}
onChange={(_, data) => onChange(data!)}
@@ -100,6 +100,10 @@ export const SortView = () => {
community: project.community,
updatedAt: project.updated_at,
membersCount: project.members_count,
size: project.size,
startDate: project.startDate,
endDate: project.endDate,
responsible: project.responsible,
});
});
+10
View File
@@ -25,6 +25,8 @@ export type Member = {
export type Status = 'ongoing' | 'proposed';
export type Size = 'small' | 'medium' | 'large';
export type BazaarProject = {
name: string;
entityRef: EntityRef;
@@ -33,10 +35,18 @@ export type BazaarProject = {
announcement: string;
updatedAt?: string;
membersCount: number;
size: Size;
startDate?: string | null;
endDate?: string | null;
responsible: string;
};
export type FormValues = {
announcement: string;
community: string;
status: string;
size: Size;
startDate?: string | null;
endDate?: string | null;
responsible: string;
};