Add edit history components
This commit is contained in:
parent
946209282c
commit
2e47d85faa
@ -0,0 +1,14 @@
|
||||
.edit-history-entry {
|
||||
border-bottom: 1px solid black;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
|
||||
.edit-history-timestamp {
|
||||
font-size: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.edit-history-username {
|
||||
font-size: 0.9rem;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { EditHistoryEntry } from '../models/edit-history-entry';
|
||||
import { arrayToDictionary, parseDate } from '../../helpers';
|
||||
import { dataFields } from '../../data_fields';
|
||||
import { CategoryEditSummary } from './category-edit-summary';
|
||||
|
||||
import './building-edit-summary.css';
|
||||
|
||||
interface BuildingEditSummaryProps {
|
||||
historyEntry: EditHistoryEntry
|
||||
}
|
||||
|
||||
function formatDate(dt: Date) {
|
||||
return dt.toLocaleString(undefined, {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
const BuildingEditSummary: React.FunctionComponent<BuildingEditSummaryProps> = props => {
|
||||
const entriesWithMetadata = Object
|
||||
.entries(props.historyEntry.forward_patch)
|
||||
.map(([key, value]) => {
|
||||
const info = dataFields[key] || {};
|
||||
return {
|
||||
title: info.title || `Unknown field (${key})`,
|
||||
category: info.category || 'Unknown category',
|
||||
value: value,
|
||||
oldValue: props.historyEntry.reverse_patch && props.historyEntry.reverse_patch[key]
|
||||
};
|
||||
});
|
||||
const entriesByCategory = arrayToDictionary(entriesWithMetadata, x => x.category);
|
||||
|
||||
|
||||
return (
|
||||
<div className="edit-history-entry">
|
||||
<h2 className="edit-history-timestamp">Edited on {formatDate(parseDate(props.historyEntry.date_trunc))}</h2>
|
||||
<h3 className="edit-history-username">By {props.historyEntry.username}</h3>
|
||||
{
|
||||
Object.entries(entriesByCategory).map(([category, fields]) => <CategoryEditSummary category={category} fields={fields} />)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
BuildingEditSummary
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
.edit-history-category-summary ul {
|
||||
list-style: none;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.edit-history-category-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.edit-history-diff {
|
||||
padding: 0 0.2rem;
|
||||
padding-top:0.15rem;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.edit-history-diff.old {
|
||||
background-color: #f8d9bc;
|
||||
color: #c24e00;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.edit-history-diff.new {
|
||||
background-color: #b6dcff;
|
||||
color: #0064c2;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import './category-edit-summary.css';
|
||||
import { FieldEditSummary } from './field-edit-summary';
|
||||
|
||||
interface CategoryEditSummaryProps {
|
||||
category: string;
|
||||
fields: {
|
||||
title: string;
|
||||
value: string;
|
||||
oldValue: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const CategoryEditSummary : React.FunctionComponent<CategoryEditSummaryProps> = props => (
|
||||
<div className='edit-history-category-summary'>
|
||||
<h3 className='edit-history-category-title'>{props.category}:</h3>
|
||||
<ul>
|
||||
{
|
||||
props.fields.map(x =>
|
||||
<li key={x.title}>
|
||||
<FieldEditSummary title={x.title} value={x.value} oldValue={x.oldValue} />
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
export {
|
||||
CategoryEditSummary
|
||||
};
|
4
app/src/frontend/building/edit-history/edit-history.css
Normal file
4
app/src/frontend/building/edit-history/edit-history.css
Normal file
@ -0,0 +1,4 @@
|
||||
.edit-history-list {
|
||||
list-style: none;
|
||||
padding-left: 1rem;
|
||||
}
|
46
app/src/frontend/building/edit-history/edit-history.tsx
Normal file
46
app/src/frontend/building/edit-history/edit-history.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { EditHistoryEntry } from '../models/edit-history-entry';
|
||||
import { BuildingEditSummary } from './building-edit-summary';
|
||||
|
||||
import './edit-history.css';
|
||||
import { Building } from '../../models/building';
|
||||
import ContainerHeader from '../container-header';
|
||||
|
||||
interface EditHistoryProps {
|
||||
building: Building;
|
||||
}
|
||||
|
||||
const EditHistory: React.FunctionComponent<EditHistoryProps> = (props) => {
|
||||
const [history, setHistory] = useState<EditHistoryEntry[]>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const res = await fetch(`/api/buildings/${props.building.building_id}/history.json`);
|
||||
const data = await res.json();
|
||||
|
||||
setHistory(data.history);
|
||||
};
|
||||
|
||||
if (props.building != undefined) { // only call fn if there is a building provided
|
||||
fetchData(); // define and call, because effect cannot return anything and an async fn always returns a Promise
|
||||
}
|
||||
}, [props.building]); // only re-run effect on building prop change
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContainerHeader title="Edit history" backLink='.' />
|
||||
|
||||
<ul className="edit-history-list">
|
||||
{history && history.map(entry => (
|
||||
<li key={`${entry.revision_id}`} className="edit-history-list-element">
|
||||
<BuildingEditSummary historyEntry={entry} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
EditHistory
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
interface FieldEditSummaryProps {
|
||||
title: string;
|
||||
value: any;
|
||||
oldValue: any;
|
||||
}
|
||||
|
||||
const FieldEditSummary: React.FunctionComponent<FieldEditSummaryProps> = props => (
|
||||
<>
|
||||
{props.title}:
|
||||
<code title="Value before edit" className='edit-history-diff old'>{props.oldValue}</code>
|
||||
|
||||
<code title="Value after edit" className='edit-history-diff new'>{props.value}</code>
|
||||
</>
|
||||
);
|
||||
|
||||
export {
|
||||
FieldEditSummary
|
||||
};
|
7
app/src/frontend/building/models/edit-history-entry.ts
Normal file
7
app/src/frontend/building/models/edit-history-entry.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface EditHistoryEntry {
|
||||
date_trunc: string;
|
||||
username: string;
|
||||
revision_id: string;
|
||||
forward_patch: object;
|
||||
reverse_patch: object;
|
||||
}
|
@ -9,13 +9,6 @@
|
||||
height: 40%;
|
||||
}
|
||||
|
||||
.info-container h2:first-child {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-left: -0.1em;
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px){
|
||||
.info-container {
|
||||
order: 0;
|
||||
@ -99,7 +92,8 @@
|
||||
color: rgb(11, 225, 225);
|
||||
}
|
||||
.icon-button.help,
|
||||
.icon-button.copy {
|
||||
.icon-button.copy,
|
||||
.icon-button.history {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.data-section label .icon-buttons .icon-button.copy {
|
||||
|
@ -36,4 +36,27 @@ function sanitiseURL(string){
|
||||
return `${url_.protocol}//${url_.hostname}${url_.pathname || ''}${url_.search || ''}${url_.hash || ''}`
|
||||
}
|
||||
|
||||
export { sanitiseURL }
|
||||
function arrayToDictionary<T>(arr: T[], keyAccessor: (obj: T) => string): {[key: string]: T[]} {
|
||||
return arr.reduce((obj, item) => {
|
||||
(obj[keyAccessor(item)] = obj[keyAccessor(item)] || []).push(item);
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string containing
|
||||
* @param isoUtcDate a date string in ISO8601 format
|
||||
*
|
||||
*/
|
||||
function parseDate(isoUtcDate: string): Date {
|
||||
const [year, month, day, hour, minute, second, millisecond] = isoUtcDate.match(/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d).(\d{3})Z$/)
|
||||
.splice(1)
|
||||
.map(x => parseInt(x, 10));
|
||||
return new Date(Date.UTC(year, month-1, day, hour, minute, second, millisecond));
|
||||
}
|
||||
|
||||
export {
|
||||
sanitiseURL,
|
||||
arrayToDictionary,
|
||||
parseDate
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user