-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #172 from impactasaurus/108
Adding record list
- Loading branch information
Showing
12 changed files
with
448 additions
and
108 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
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,98 @@ | ||
import * as React from 'react'; | ||
import {Table, IconProps, Label, Icon, Popup} from 'semantic-ui-react'; | ||
import './style.less'; | ||
import {IMeeting, sortMeetingsByConducted} from '../../models/meeting'; | ||
import {renderArray, renderArrayForArray} from '../../helpers/react'; | ||
import {getHumanisedDate} from '../../helpers/moment'; | ||
|
||
interface IProp { | ||
meetings: IMeeting[]; | ||
} | ||
|
||
interface IState { | ||
open: string[]; | ||
} | ||
|
||
class RecordList extends React.Component<IProp, IState> { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
open: [], | ||
}; | ||
|
||
this.renderRecord = this.renderRecord.bind(this); | ||
this.toggleRecord = this.toggleRecord.bind(this); | ||
} | ||
|
||
private toggleRecord(r: IMeeting): () => void { | ||
return () => { | ||
if (this.state.open.indexOf(r.id) === -1) { | ||
this.setState({ | ||
open: this.state.open.concat(r.id), | ||
}); | ||
} else { | ||
this.setState({ | ||
open: this.state.open.filter((i) => i !== r.id), | ||
}); | ||
} | ||
}; | ||
} | ||
|
||
private renderTag(t: string): JSX.Element { | ||
return ( | ||
<Label>{t}</Label> | ||
); | ||
} | ||
|
||
private renderRecord(r: IMeeting, idx: number): JSX.Element[] { | ||
let clz = 'record'; | ||
if (idx % 2 !== 0) { | ||
clz += ' stripe'; | ||
} | ||
const open = this.state.open.find((o) => o === r.id); | ||
const iconProps: IconProps = {}; | ||
if (!open) { | ||
iconProps.rotated = 'counterclockwise'; | ||
} | ||
let incomplete = (<span/>); | ||
if (r.incomplete) { | ||
incomplete = (<Popup trigger={<Icon name="hourglass half"/>} content="Incomplete" /> ); | ||
} | ||
return [ | ||
<Table.Row className={clz} key={r.id}> | ||
<Table.Cell className="name" onClick={this.toggleRecord(r)}> | ||
{incomplete} | ||
<span>{getHumanisedDate(new Date(r.conducted))}</span> | ||
</Table.Cell> | ||
<Table.Cell>{r.outcomeSet.name}</Table.Cell> | ||
<Table.Cell>{renderArray(this.renderTag, r.tags)}</Table.Cell> | ||
<Table.Cell>{r.user}</Table.Cell> | ||
<Table.Cell>Coming Soon!</Table.Cell> | ||
</Table.Row>, | ||
]; | ||
} | ||
|
||
public render() { | ||
return ( | ||
<div id="record-list"> | ||
<Table celled={true} className="record-list-table"> | ||
<Table.Header> | ||
<Table.Row> | ||
<Table.HeaderCell>Date</Table.HeaderCell> | ||
<Table.HeaderCell>Questionnaire</Table.HeaderCell> | ||
<Table.HeaderCell>Tags</Table.HeaderCell> | ||
<Table.HeaderCell>Conducted</Table.HeaderCell> | ||
<Table.HeaderCell>Actions</Table.HeaderCell> | ||
</Table.Row> | ||
</Table.Header> | ||
<Table.Body> | ||
{renderArrayForArray(this.renderRecord, sortMeetingsByConducted(this.props.meetings, false))} | ||
</Table.Body> | ||
</Table> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export { RecordList }; |
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,25 @@ | ||
#record-list { | ||
.record-list-table { | ||
tr.record.stripe { | ||
background-color: rgba(0, 0, 50, 0.02); | ||
} | ||
.record-inner { | ||
box-shadow: inset -4px 15px 10px -11px rgba(0,0,0,0.04); | ||
background-color: #FDFDFD; | ||
|
||
& > td { | ||
padding: 30px; | ||
border-bottom: 1px solid #ccc; | ||
} | ||
|
||
table.question-list-table { | ||
tr th, tr td { | ||
border-left: none; | ||
} | ||
th, td { | ||
padding: 0.4em 0.6em; | ||
} | ||
} | ||
} | ||
} | ||
} |
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,138 @@ | ||
import * as React from 'react'; | ||
import { Loader, Message } from 'semantic-ui-react'; | ||
import {QuestionSetSelect} from 'components/QuestionSetSelect'; | ||
import {VizControlPanel} from 'components/VizControlPanel'; | ||
import {setURL} from 'modules/url'; | ||
import { bindActionCreators } from 'redux'; | ||
import {IStore} from 'redux/IStore'; | ||
import {IURLConnector} from 'redux/modules/url'; | ||
import {Aggregation, Visualisation, getAggregation, getVisualisation, getSelectedQuestionSetID} from 'models/pref'; | ||
import {getMeetings, IMeetingResult} from 'apollo/modules/meetings'; | ||
import {IMeeting} from 'models/meeting'; | ||
import {MeetingRadar} from 'components/MeetingRadar'; | ||
import {MeetingTable} from 'components/MeetingTable'; | ||
import {isBeneficiaryUser} from 'modules/user'; | ||
import {MeetingGraph} from '../../components/MeetingGraph'; | ||
const { connect } = require('react-redux'); | ||
|
||
interface IProps extends IURLConnector { | ||
params: { | ||
id: string, | ||
}; | ||
vis?: Visualisation; | ||
agg?: Aggregation; | ||
selectedQuestionSetID?: string; | ||
data?: IMeetingResult; | ||
isCategoryAgPossible?: boolean; | ||
} | ||
|
||
function isCategoryAggregationAvailable(meetings: IMeeting[], selectedQuestionSetID: string|undefined): boolean { | ||
if (!Array.isArray(meetings) || meetings.length === 0) { | ||
return false; | ||
} | ||
const meetingsBelongingToSelectedQS = meetings.filter((m) => { | ||
return selectedQuestionSetID !== undefined && m.outcomeSetID === selectedQuestionSetID; | ||
}); | ||
const meetingsWithCategories = meetingsBelongingToSelectedQS.filter((m) => { | ||
return m.outcomeSet.categories.length > 0; | ||
}); | ||
return meetingsWithCategories.length > 0; | ||
} | ||
|
||
function getQuestionSetOptions(ms: IMeeting[]): string[] { | ||
if (!Array.isArray(ms)) { | ||
return []; | ||
} | ||
return ms.map((m) => m.outcomeSetID); | ||
} | ||
|
||
function filterMeetings(m: IMeeting[], questionSetID: string): IMeeting[] { | ||
return m.filter((m) => m.outcomeSetID === questionSetID); | ||
} | ||
|
||
@connect((state: IStore, ownProps: IProps) => { | ||
const selectedQuestionSetID = getSelectedQuestionSetID(state.pref); | ||
const canCatAg = isCategoryAggregationAvailable(ownProps.data.getMeetings, selectedQuestionSetID); | ||
return { | ||
vis: getVisualisation(state.pref, true), | ||
agg: getAggregation(state.pref, canCatAg), | ||
isCategoryAgPossible: canCatAg, | ||
selectedQuestionSetID, | ||
isBeneficiary: isBeneficiaryUser(state.user), | ||
}; | ||
}, (dispatch) => ({ | ||
setURL: bindActionCreators(setURL, dispatch), | ||
})) | ||
class JourneyInner extends React.Component<IProps, any> { | ||
|
||
constructor(props) { | ||
super(props); | ||
this.renderVis = this.renderVis.bind(this); | ||
this.renderJourney = this.renderJourney.bind(this); | ||
} | ||
|
||
private renderVis(): JSX.Element { | ||
const { data: { getMeetings }, vis, selectedQuestionSetID, agg } = this.props; | ||
const meetings = filterMeetings(getMeetings, selectedQuestionSetID); | ||
|
||
if (vis === Visualisation.RADAR) { | ||
return ( | ||
<MeetingRadar aggregation={agg} meetings={meetings} /> | ||
); | ||
} | ||
if (vis === Visualisation.GRAPH) { | ||
return ( | ||
<MeetingGraph meetings={meetings} aggregation={agg}/> | ||
); | ||
} | ||
return ( | ||
<MeetingTable aggregation={agg} meetings={meetings} /> | ||
); | ||
} | ||
|
||
private renderJourney(): JSX.Element { | ||
if (this.props.data.loading) { | ||
return ( | ||
<Loader active={true} inline="centered" /> | ||
); | ||
} | ||
if (this.props.data.error !== undefined) { | ||
return ( | ||
<Message error={true}> | ||
<Message.Header>Error</Message.Header> | ||
<div>Failed to load records</div> | ||
</Message> | ||
); | ||
} | ||
if (!Array.isArray(this.props.data.getMeetings) || this.props.data.getMeetings.length === 0) { | ||
return ( | ||
<p>No complete meetings found for beneficiary {this.props.params.id}</p> | ||
); | ||
} | ||
return ( | ||
<div> | ||
<VizControlPanel canCategoryAg={this.props.isCategoryAgPossible} allowGraph={true}/> | ||
<QuestionSetSelect | ||
allowedQuestionSetIDs={getQuestionSetOptions(this.props.data.getMeetings)} | ||
autoSelectFirst={true} | ||
/> | ||
{this.renderVis()} | ||
</div> | ||
); | ||
} | ||
|
||
public render() { | ||
if(this.props.params.id === undefined) { | ||
return (<div />); | ||
} | ||
|
||
return ( | ||
<div id="journey"> | ||
{this.renderJourney()} | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
const Journey = getMeetings<IProps>((p) => p.params.id)(JourneyInner); | ||
export { Journey } |
Oops, something went wrong.