-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CDC #13 - Adding places list and edit pages
- Loading branch information
dleadbetter
committed
Sep 11, 2023
1 parent
16294e2
commit 20e87ff
Showing
14 changed files
with
522 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// @flow | ||
|
||
import { AssociatedDropdown } from '@performant-software/semantic-components'; | ||
import React, { | ||
useCallback, | ||
useEffect, | ||
useState, | ||
type AbstractComponent | ||
} from 'react'; | ||
import { useParams } from 'react-router-dom'; | ||
import type { Project as ProjectType } from '../types/Project'; | ||
import type { Projectable as ProjectableType } from '../types/Projectable'; | ||
import ProjectsService from '../services/Projects'; | ||
import ProjectTransform from '../transforms/Project'; | ||
|
||
type Props = { | ||
item: ProjectableType, | ||
onSetState: (item: any) => void | ||
}; | ||
|
||
const OwnableDropdown: AbstractComponent<any> = (props: Props) => { | ||
const { projectId } = useParams(); | ||
const [currentProject, setCurrentProject] = useState(); | ||
|
||
/** | ||
* If we're in the context of a single project, only return the current project. | ||
* Otherwise call the `/api/projects` endpoint. | ||
* | ||
* @type {function(string): *} | ||
*/ | ||
const onSearch = useCallback((search: string) => { | ||
let promise; | ||
|
||
if (currentProject) { | ||
promise = Promise.resolve({ data: { projects: [currentProject] } }); | ||
} else { | ||
promise = ProjectsService.fetchAll({ search }); | ||
} | ||
|
||
return promise; | ||
}, [currentProject]); | ||
|
||
/** | ||
* Sets the project on the state's `project_item`. | ||
* | ||
* @type {function(Project): *} | ||
*/ | ||
const onSelection = useCallback((project: ProjectType) => props.onSetState({ | ||
project_item: { | ||
project_id: project.id, | ||
project | ||
} | ||
}), []); | ||
|
||
/** | ||
* If we're in the context of a single project, load the project and set it on the state. | ||
*/ | ||
useEffect(() => { | ||
if (projectId) { | ||
ProjectsService | ||
.fetchOne(projectId) | ||
.then(({ data }) => setCurrentProject(data.project)); | ||
} | ||
}, []); | ||
|
||
/** | ||
* If we're adding a new record, select the current project by default. | ||
*/ | ||
useEffect(() => { | ||
if (currentProject && !props.item.id) { | ||
onSelection(currentProject); | ||
} | ||
}, [currentProject]); | ||
|
||
return ( | ||
<AssociatedDropdown | ||
collectionName='projects' | ||
onSearch={onSearch} | ||
onSelection={onSelection} | ||
renderOption={(project) => ProjectTransform.toDropdown(project)} | ||
searchQuery={props.item.project_item?.project?.name} | ||
value={props.item.project_item?.project_id} | ||
/> | ||
); | ||
}; | ||
|
||
export default OwnableDropdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// @flow | ||
|
||
import type { EditContainerProps } from '@performant-software/shared-components/types'; | ||
import React, { type AbstractComponent } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { Form, Modal } from 'semantic-ui-react'; | ||
import type { PlaceName as PlaceNameType } from '../types/Place'; | ||
|
||
type Props = EditContainerProps & { | ||
item: PlaceNameType | ||
}; | ||
|
||
const PlaceNameModal: AbstractComponent<any> = (props: Props) => { | ||
const { t } = useTranslation(); | ||
|
||
return ( | ||
<Modal | ||
as={Form} | ||
centered={false} | ||
open | ||
> | ||
<Modal.Header | ||
content={props.item.id | ||
? t('PlaceNameModal.title.edit') | ||
: t('PlaceNameModal.title.add')} | ||
/> | ||
<Modal.Content> | ||
<Form.Input | ||
autoFocus | ||
error={props.isError('name')} | ||
label={t('PlaceNameModal.labels.name')} | ||
required={props.isRequired('name')} | ||
onChange={props.onTextInputChange.bind(this, 'name')} | ||
value={props.item.name} | ||
/> | ||
<Form.Checkbox | ||
checked={props.item.primary} | ||
label={t('PlaceNameModal.labels.primary')} | ||
onChange={props.onCheckboxInputChange.bind(this, 'primary')} | ||
/> | ||
</Modal.Content> | ||
{ props.children } | ||
</Modal> | ||
); | ||
}; | ||
|
||
export default PlaceNameModal; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// @flow | ||
|
||
import { | ||
BooleanIcon, | ||
EmbeddedList, | ||
SimpleEditPage | ||
} from '@performant-software/semantic-components'; | ||
import type { EditContainerProps } from '@performant-software/shared-components/types'; | ||
import React, { type AbstractComponent } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { Form, Header } from 'semantic-ui-react'; | ||
import OwnableDropdown from '../components/OwnableDropdown'; | ||
import type { Place as PlaceType } from '../types/Place'; | ||
import PlaceNameModal from '../components/PlaceNameModal'; | ||
import PlacesService from '../services/Places'; | ||
import Validation from '../utils/Validation'; | ||
import withReactRouterEditPage from '../hooks/ReactRouterEditPage'; | ||
|
||
type Props = EditContainerProps & { | ||
item: PlaceType | ||
}; | ||
|
||
const PlaceForm = (props: Props) => { | ||
const { t } = useTranslation(); | ||
|
||
return ( | ||
<SimpleEditPage | ||
{...props} | ||
> | ||
<SimpleEditPage.Tab | ||
key='default' | ||
> | ||
<Form.Input | ||
label={t('Place.labels.project')} | ||
required | ||
> | ||
<OwnableDropdown | ||
item={props.item} | ||
onSetState={props.onSetState} | ||
/> | ||
</Form.Input> | ||
<Header | ||
content={t('Place.labels.names')} | ||
/> | ||
<EmbeddedList | ||
actions={[{ | ||
name: 'edit' | ||
}, { | ||
name: 'delete' | ||
}]} | ||
columns={[{ | ||
name: 'name', | ||
label: t('Place.placeNames.columns.name') | ||
}, { | ||
name: 'primary', | ||
label: t('Place.placeNames.columns.primary'), | ||
render: (placeName) => <BooleanIcon value={placeName.primary} /> | ||
}]} | ||
items={props.item.place_names} | ||
modal={{ | ||
component: PlaceNameModal | ||
}} | ||
onSave={props.onSaveChildAssociation.bind(this, 'place_names')} | ||
onDelete={props.onDeleteChildAssociation.bind(this, 'place_names')} | ||
/> | ||
</SimpleEditPage.Tab> | ||
</SimpleEditPage> | ||
); | ||
}; | ||
|
||
const Place: AbstractComponent<any> = withReactRouterEditPage(PlaceForm, { | ||
id: 'placeId', | ||
onInitialize: (id) => ( | ||
PlacesService | ||
.fetchOne(id) | ||
.then(({ data }) => data.place) | ||
), | ||
onSave: (place) => ( | ||
PlacesService | ||
.save(place) | ||
.then(({ data }) => data.place) | ||
), | ||
resolveValidationError: Validation.resolveUpdateError.bind(this) | ||
}); | ||
|
||
export default Place; |
Oops, something went wrong.