diff --git a/app/src/components/DateProgressBar/i18n.json b/app/src/components/DateProgressBar/i18n.json new file mode 100644 index 0000000000..03fdccac7b --- /dev/null +++ b/app/src/components/DateProgressBar/i18n.json @@ -0,0 +1,9 @@ +{ + "namespace": "dateProgressBar", + "strings": { + "startDateLabel": "Start", + "endDateLabel": "End", + "operationTimelineLabel": "Operation Timeline", + "imminentDrefLabel": "Imminent DREF" + } +} \ No newline at end of file diff --git a/app/src/components/DateProgressBar/index.tsx b/app/src/components/DateProgressBar/index.tsx new file mode 100644 index 0000000000..b3df2f1cec --- /dev/null +++ b/app/src/components/DateProgressBar/index.tsx @@ -0,0 +1,121 @@ +import { useMemo } from 'react'; +import { ChartPoint } from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; + +interface Props { + startDate: string; + endDate: string; +} + +function DateProgressBar(props: Props) { + const { + startDate, + endDate, + } = props; + const strings = useTranslation(i18n); + + const { + progress, + isActive, + startDateValue, + endDateValue, + } = useMemo(() => { + const start = new Date(startDate); + const end = new Date(endDate); + const today = new Date(); + + const startTime = start.getTime(); + const endTime = end.getTime(); + const todayTime = today.getTime(); + + const totalSpan = endTime - startTime; + const val = totalSpan > 0 ? ((todayTime - startTime) / totalSpan) * 100 : 0; + + return { + progress: Math.min(100, Math.max(0, val)), + isActive: todayTime >= startTime && todayTime <= endTime, + startDateValue: start.toLocaleDateString(), + endDateValue: end.toLocaleDateString(), + }; + }, [startDate, endDate]); + + const width = 200; + const height = 20; + const centerY = height / 2; + const progressX = (progress / 100) * width; + + return ( +
+
+
+ {strings.operationTimelineLabel} +
+
+ {strings.imminentDrefLabel} +
+ + + + + + {isActive && ( + + )} + +
+
+ {strings.startDateLabel} +
+
+ {startDateValue} +
+
+ +
+
+ {strings.endDateLabel} +
+
+ {endDateValue} +
+
+
+
+ ); +} + +export default DateProgressBar; diff --git a/app/src/components/DateProgressBar/styles.module.css b/app/src/components/DateProgressBar/styles.module.css new file mode 100644 index 0000000000..16bd47ebf5 --- /dev/null +++ b/app/src/components/DateProgressBar/styles.module.css @@ -0,0 +1,58 @@ +.container { + background-color: var(--go-ui-color-white); + padding: var(--go-ui-spacing-xl); + width: auto; + + .timeline-wrapper { + position: relative; + padding: var(--go-ui-spacing-xl) 0; + width: 100%; + + .svg { + display: block; + width: 100%; + overflow: visible; + } + + .track { + stroke: var(--go-ui-color-separator); + stroke-width: 2; + } + + .progress { + stroke: var(--go-ui-color-primary-red); + stroke-width: 3; + stroke-linecap: round; + } + + .dashed-separator { + stroke: var(--go-ui-color-separator); + stroke-width: 1; + stroke-dasharray: 4 2; + } + + .point-outline { + color: var(--go-ui-color-primary-red); + } + + .title-start, .label-start { + position: absolute; + white-space: nowrap; + } + + .title-end, .label-end { + position: absolute; + right: 0; + text-align: right; + white-space: nowrap; + } + + .title-start, .title-end { + top: 0; + } + + .label-start, .label-end { + bottom: 0; + } + } +} \ No newline at end of file diff --git a/app/src/components/TimelineBar/i18n.json b/app/src/components/TimelineBar/i18n.json new file mode 100644 index 0000000000..9a56fcd00b --- /dev/null +++ b/app/src/components/TimelineBar/i18n.json @@ -0,0 +1,9 @@ +{ + "namespace": "timelineBar", + "strings": { + "startDateLabel": "Start of Imminent Ops", + "todayLabel": "Today", + "forecastLabel": "Forecast", + "operationEnd": "End of the Operation" + } +} \ No newline at end of file diff --git a/app/src/components/TimelineBar/index.tsx b/app/src/components/TimelineBar/index.tsx new file mode 100644 index 0000000000..ca35813d94 --- /dev/null +++ b/app/src/components/TimelineBar/index.tsx @@ -0,0 +1,104 @@ +import { + Container, + TextOutput, +} from '@ifrc-go/ui'; +import { useTranslation } from '@ifrc-go/ui/hooks'; +import { + formatDate, + getNumberOfDays, +} from '@ifrc-go/ui/utils'; + +import i18n from './i18n.json'; +import styles from './styles.module.css'; + +interface Props { + imminentStartDate: string | Date; + operationEndDate: string | Date; + forecastDate: string | Date; + heading: string; +} + +function TimelineBar(props: Props) { + const { + imminentStartDate, + operationEndDate, + forecastDate, + heading, + } = props; + + const strings = useTranslation(i18n); + const start = new Date(imminentStartDate); + const end = new Date(operationEndDate); + const fDate = new Date(forecastDate); + const today = new Date(); + + const totalDuration = getNumberOfDays(start, end) || 1; + + const getPosition = (targetDate: Date) => { + const daysFromStart = getNumberOfDays(start, targetDate) ?? 0; + const percentage = (daysFromStart / totalDuration) * 100; + return Math.min(Math.max(percentage, 0), 100); + }; + + return ( + +
+
+
+ + {strings.todayLabel} + +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+ + ); +} + +export default TimelineBar; diff --git a/app/src/components/TimelineBar/styles.module.css b/app/src/components/TimelineBar/styles.module.css new file mode 100644 index 0000000000..e3fe561add --- /dev/null +++ b/app/src/components/TimelineBar/styles.module.css @@ -0,0 +1,65 @@ +.timeline-container { + position: relative; + box-shadow: var(--go-ui-box-shadow-xs); + width: 100%; + height: 20rem; + + .main-line { + position: absolute; + top: 50%; + z-index: 1; + background-color: var(--go-ui-color-black); + width: 100%; + height: var(--go-ui-width-separator-thin); + } + + .today-indicator { + position: absolute; + top: 10%; + bottom: 10%; + z-index: 2; + background-color: var(--go-ui-color-black); + width: var(--go-ui-width-separator-thin); + + .today-label { + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%); + font-weight: var(--go-ui-font-weight-medium); + } + } + + /* NOTE: Only camelCase is working here */ + .startMilestone, .endMilestone, .forecastMilestone { + display: flex; + position: absolute; + flex-direction: column; + z-index: 3; + } + + .startMilestone, .endMilestone { + bottom: 50%; + align-items: flex-start; + } + + .endMilestone { + align-items: flex-end; + } + + .forecastMilestone { + top: 50%; + align-items: flex-start; + } + + .marker-top, .marker-bottom { + background-color: var(--go-ui-color-black); + width: var(--go-ui-width-separator-thin); + height: 5rem; + } + + .red-label { + color: var(--go-ui-color-red); + font-weight: var(--go-ui-font-weight-semibold); + } +} diff --git a/app/src/views/Emergency/i18n.json b/app/src/views/Emergency/i18n.json index 0e4d37c8c1..6437836e9d 100644 --- a/app/src/views/Emergency/i18n.json +++ b/app/src/views/Emergency/i18n.json @@ -14,6 +14,8 @@ "emergencies": "Emergencies", "emergencyEdit": "Edit Event", "emergencyFollow":"Follow", - "emergencyUnfollow":"Unfollow" + "emergencyUnfollow":"Unfollow", + "fundingRequirements": "Funding Requirements (CHF)", + "targetedPopulation": "Targeted Population" } } diff --git a/app/src/views/Emergency/index.tsx b/app/src/views/Emergency/index.tsx index 8be8a851fa..653d4efb21 100644 --- a/app/src/views/Emergency/index.tsx +++ b/app/src/views/Emergency/index.tsx @@ -16,8 +16,12 @@ import { Breadcrumbs, Button, KeyFigureView, + Label, ListView, NavigationTabList, + NumberOutput, + ProgressBar, + TextOutput, } from '@ifrc-go/ui'; import { useTranslation } from '@ifrc-go/ui/hooks'; import { @@ -30,6 +34,7 @@ import { listToMap, } from '@togglecorp/fujs'; +import DateProgressBar from '#components/DateProgressBar'; import Link from '#components/Link'; import NavigationTab from '#components/NavigationTab'; import Page from '#components/Page'; @@ -274,26 +279,74 @@ export function Component() { )} heading={emergencyResponse?.name ?? '--'} description={( - <> - + - {region?.region_name} - - + {region?.region_name} + + + {country?.name} + + + - {country?.name} - - + {/* TODO: Add the value here */} + + {/* TODO: Add the value here */} + + + {/* TODO: Add the value here */} + + + )} + /> + {/* TODO: Add the value here */} + + + )} info={(