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
@@ -18,14 +18,17 @@ import { DatabaseHandler } from './DatabaseHandler';
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
const bazaarProject: any = {
name: 'name',
entityRef: 'ref',
name: 'n1',
entityRef: 'ref1',
community: '',
status: 'proposed',
announcement: 'a',
membersCount: 0,
startDate: '2021-11-07T13:27:00.000Z',
endDate: null,
size: 'small',
responsible: 'r',
};
describe('DatabaseHandler', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
@@ -33,18 +36,40 @@ describe('DatabaseHandler', () => {
async function createDatabaseHandler(databaseId: TestDatabaseId) {
const knex = await databases.init(databaseId);
return await DatabaseHandler.create({ database: knex });
return {
knex,
dbHandler: await DatabaseHandler.create({ database: knex }),
};
}
it.each(databases.eachSupportedId())(
'should do a full sync with the locations on connect, %p',
'should insert and get entity, %p',
async databaseId => {
const db = await createDatabaseHandler(databaseId);
await db.insertMetadata(bazaarProject);
const { knex, dbHandler } = await createDatabaseHandler(databaseId);
const entities = await db.getEntities();
expect(entities.length).toEqual(1);
expect(entities[0].entity_ref).toEqual(bazaarProject.entityRef);
await knex('metadata').insert({
entity_ref: bazaarProject.entityRef,
name: bazaarProject.name,
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
updated_at: new Date().toISOString(),
start_date: bazaarProject.startDate,
end_date: bazaarProject.endDate,
size: bazaarProject.size,
responsible: bazaarProject.responsible,
});
const res = await dbHandler.getMetadata('ref1');
expect(res).toHaveLength(1);
expect(res[0].announcement).toEqual('a');
expect(res[0].community).toEqual('');
expect(res[0].status).toEqual('proposed');
expect(res[0].start_date).toEqual('2021-11-07T13:27:00.000Z');
expect(res[0].end_date).toEqual(null);
expect(res[0].size).toEqual('small');
expect(res[0].responsible).toEqual('r');
},
60_000,
);
@@ -43,6 +43,20 @@ export class DatabaseHandler {
this.database = options.database;
}
private columns = [
'members.entity_ref',
'metadata.entity_ref',
'metadata.name',
'metadata.announcement',
'metadata.status',
'metadata.updated_at',
'metadata.community',
'metadata.size',
'metadata.start_date',
'metadata.end_date',
'metadata.responsible',
];
async getMembers(entityRef: string) {
return await this.database
.select('*')
@@ -72,25 +86,25 @@ export class DatabaseHandler {
'coalesce(count(members.entity_ref), 0) as members_count',
);
const columns = [
'members.entity_ref',
'metadata.entity_ref',
'metadata.name',
'metadata.announcement',
'metadata.status',
'metadata.updated_at',
'metadata.community',
];
return await this.database('metadata')
.select([...columns, coalesce])
.select([...this.columns, coalesce])
.where({ 'metadata.entity_ref': entityRef })
.groupBy(columns)
.groupBy(this.columns)
.leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
}
async insertMetadata(bazaarProject: any) {
const { name, entityRef, community, announcement, status } = bazaarProject;
const {
name,
entityRef,
community,
announcement,
status,
size,
startDate,
endDate,
responsible,
} = bazaarProject;
await this.database
.insert({
@@ -100,12 +114,25 @@ export class DatabaseHandler {
announcement: announcement,
status: status,
updated_at: new Date().toISOString(),
size,
start_date: startDate,
end_date: endDate,
responsible,
})
.into('metadata');
}
async updateMetadata(bazaarProject: any) {
const { entityRef, community, announcement, status } = bazaarProject;
const {
entityRef,
community,
announcement,
status,
size,
startDate,
endDate,
responsible,
} = bazaarProject;
return await this.database('metadata')
.where({ entity_ref: entityRef })
@@ -114,6 +141,10 @@ export class DatabaseHandler {
community: community,
status: status,
updated_at: new Date().toISOString(),
size,
start_date: startDate,
end_date: endDate,
responsible,
});
}
@@ -128,18 +159,9 @@ export class DatabaseHandler {
'coalesce(count(members.entity_ref), 0) as members_count',
);
const columns = [
'members.entity_ref',
'metadata.entity_ref',
'metadata.name',
'metadata.announcement',
'metadata.status',
'metadata.updated_at',
'metadata.community',
];
return await this.database('metadata')
.select([...columns, coalesce])
.groupBy(columns)
.select([...this.columns, coalesce])
.groupBy(this.columns)
.leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
}
}
+21 -10
View File
@@ -58,7 +58,7 @@ const overviewContent = (
<EntityAboutCard variant="gridItem" />
</Grid>
+ <Grid item sm={4}>
+ <Grid item sm={6}>
+ <EntityBazaarInfoCard />
+ </Grid>
@@ -75,12 +75,23 @@ The latest modified Bazaar projects are displayed in the Bazaar landing page, lo
### Workflow
To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form. You will be asked to add an announcement for new team members. The purpose of the announcement is for you to present your ideas and what skills you are looking for. Further you need to provide the status of the project.
To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form.
The following fields are mandatory:
- announcement - present your idea and what skills you are looking for
- status - whether or not the project has started
- size - small, medium or large
The other fields are:
- start date
- end date
- responsible - main contact person of the project
- community link - link where the project members can chat, e.g. Teams or Discord link
When the project is added, you will see the Bazaar information in the Bazaar card on the entity page. There you can join a project, edit or delete it.
![workflow](media/bazaar_demo.gif)
### Database
The metadata related to the Bazaar is stored in a database. Right now there are two tables, one for storing the metadata and one for storing the members of a Bazaar project.
@@ -92,6 +103,10 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- announcement - announcement of the project and its current need of skills/team member
- status - status of the project, 'proposed' or 'ongoing'
- updated_at - date when the Bazaar information was last modified (ISO 8601 format)
- size - small, medium or large
- start_date - date when the project is estimated to start (ISO 8601 format)
- end_date - date when the project is estimated to end (ISO 8601 format)
- responsible - main contact person of the project
**members**:
@@ -107,12 +122,8 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- Bazaar landing page
- Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities, projects or tags you are following etc.
- Make it possible to sort the project based on the number of members
- Bazaar card
- Make it possible to follow tags/projects
- Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities etc.
- Make it possible to sort the project based on e.g. the number of members
- Bazaar tab on the EntityPage
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 KiB

After

Width:  |  Height:  |  Size: 287 KiB

+3 -1
View File
@@ -22,7 +22,7 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.5",
"@backstage/cli": "^0.8.0",
"@backstage/cli": "^0.8.1",
"@backstage/core-components": "^0.7.2",
"@backstage/core-plugin-api": "^0.1.12",
"@backstage/plugin-catalog": "^0.7.2",
@@ -30,7 +30,9 @@
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"@material-ui/pickers": "^3.3.10",
"@testing-library/jest-dom": "^5.10.1",
"@date-io/luxon": "1.x",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
@@ -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;
};