Skip to content

Commit

Permalink
feat: add activated-at column to frontend detours list
Browse files Browse the repository at this point in the history
  • Loading branch information
firestack committed Dec 21, 2024
1 parent ca99c97 commit 69c0258
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 106 deletions.
66 changes: 58 additions & 8 deletions assets/src/components/detourListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { useCallback, useState } from "react"
import { DetoursTable, DetourStatus } from "./detoursTable"
import {
DetoursTable,
DetourStatus,
timestampLabelFromStatus,
} from "./detoursTable"
import userInTestGroup, { TestGroups } from "../userInTestGroup"
import { Button } from "react-bootstrap"
import { Button, Table } from "react-bootstrap"
import {
GlobeAmericas,
LockFill,
Expand All @@ -16,6 +20,9 @@ import { isErr, isOk } from "../util/result"
import { isValidSnapshot } from "../util/isValidSnapshot"
import { createDetourMachine } from "../models/createDetourMachine"
import { joinClasses } from "../helpers/dom"
import { RoutePill } from "./routePill"
import { timeAgoLabelForDates } from "../util/dateTime"
import useCurrentTime from "../hooks/useCurrentTime"

export const DetourListPage = () => {
const [showDetourModal, setShowDetourModal] = useState(false)
Expand Down Expand Up @@ -55,6 +62,8 @@ export const DetourListPage = () => {
setShowDetourModal(false)
}

const epochNow = useCurrentTime()

return (
<div className="c-detour-list-page h-100 overflow-y-auto p-0 p-md-4 bg-white">
{userInTestGroup(TestGroups.DetoursPilot) && (
Expand All @@ -75,12 +84,53 @@ export const DetourListPage = () => {
visibility="All Skate users"
classNames={["d-flex"]}
/>
<DetoursTable
data={detours.active}
status={DetourStatus.Active}
onOpenDetour={onOpenDetour}
classNames={["mb-5"]}
/>
<Table
hover={!!detours.active}
className={"mb-5 c-detours-table"}
variant={"active-detour"}
>
<thead className="u-hide-for-mobile">
<tr>
<th className="px-3 py-4">Route and direction</th>
<th className="px-3 py-4 u-hide-for-mobile">
Starting Intersection
</th>
<th className="px-3 py-4 u-hide-for-mobile">
{timestampLabelFromStatus(DetourStatus.Active)}
</th>
</tr>
</thead>
<tbody>
{detours.active
? detours.active.map((detour, index) => (
<tr
key={index}
onClick={() => onOpenDetour(detour.details.id)}
>
<td className="align-middle p-3">
<div className="d-flex">
<RoutePill routeName={detour.details.route} />
<div className="c-detours-table__route-info-text d-inline-block">
<div className="pb-1 fs-4 fw-semibold">
{detour.details.name}
</div>
<div className="c-detours-table__route-info-direction fs-6">
{detour.details.direction}
</div>
</div>
</div>
</td>
<td className="align-middle p-3 u-hide-for-mobile">
{detour.details.intersection}
</td>
<td className="align-middle p-3 u-hide-for-mobile">
{timeAgoLabelForDates(detour.activatedAt, epochNow)}
</td>
</tr>
))
: null}
</tbody>
</Table>
{userInTestGroup(TestGroups.DetoursPilot) && (
<>
<Title
Expand Down
30 changes: 26 additions & 4 deletions assets/src/models/detoursList.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { array, Infer, nullable, number, string, type } from "superstruct"
import {
array,
coerce,
date,
Infer,
nullable,
number,
string,
type,
} from "superstruct"

export type DetourId = number
export interface SimpleDetour {
Expand All @@ -10,6 +19,11 @@ export interface SimpleDetour {
updatedAt: number
}

export interface ActivatedDetour {
activatedAt: Date
details: SimpleDetour
}

export const detourId = number()
export const SimpleDetourData = type({
id: detourId,
Expand All @@ -20,6 +34,11 @@ export const SimpleDetourData = type({
updated_at: number(),
})

export const ActivatedDetourData = type({
activated_at: coerce(date(), string(), (dateStr) => new Date(dateStr)),
details: SimpleDetourData,
})

export type SimpleDetourData = Infer<typeof SimpleDetourData>

export const simpleDetourFromData = (
Expand All @@ -34,13 +53,13 @@ export const simpleDetourFromData = (
})

export interface GroupedSimpleDetours {
active?: SimpleDetour[]
active?: ActivatedDetour[]
draft?: SimpleDetour[]
past?: SimpleDetour[]
}

export const GroupedDetoursData = type({
active: nullable(array(SimpleDetourData)),
active: nullable(array(ActivatedDetourData)),
draft: nullable(array(SimpleDetourData)),
past: nullable(array(SimpleDetourData)),
})
Expand All @@ -50,7 +69,10 @@ export type GroupedDetoursData = Infer<typeof GroupedDetoursData>
export const groupedDetoursFromData = (
groupedDetours: GroupedDetoursData
): GroupedSimpleDetours => ({
active: groupedDetours.active?.map((detour) => simpleDetourFromData(detour)),
active: groupedDetours.active?.map((detour) => ({
details: simpleDetourFromData(detour.details),
activatedAt: detour.activated_at,
})),
draft: groupedDetours.draft?.map((detour) => simpleDetourFromData(detour)),
past: groupedDetours.past?.map((detour) => simpleDetourFromData(detour)),
})
18 changes: 18 additions & 0 deletions assets/src/util/dateTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,21 @@ export const timeAgoLabel = (

return diffHours >= 1 ? `${diffHours} hours ago` : `${diffMinutes} min ago`
}

const second = 1000
const minute = second * 60
const hour = minute * 60

export const timeAgoLabelForDates = (start: Date, end: Date) => {
const length = end.valueOf() - start.valueOf()

if (length > 1 * hour) {
return `${Math.floor(length / hour)} hours ago`
}

if (length > 1 * minute) {
return `${Math.floor(length / minute)} min ago`
}

return "Just now"
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ exports[`Detours Page: Open a Detour renders detour details in an open drawer on
<td
class="align-middle p-3 u-hide-for-mobile"
>
26 hours ago
Just now
</td>
</tr>
<tr>
Expand Down Expand Up @@ -163,7 +163,7 @@ exports[`Detours Page: Open a Detour renders detour details in an open drawer on
<td
class="align-middle p-3 u-hide-for-mobile"
>
26 hours ago
Just now
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -971,7 +971,7 @@ exports[`Detours Page: Open a Detour renders detour details modal to match mocke
<td
class="align-middle p-3 u-hide-for-mobile"
>
26 hours ago
Just now
</td>
</tr>
<tr>
Expand Down Expand Up @@ -1010,7 +1010,7 @@ exports[`Detours Page: Open a Detour renders detour details modal to match mocke
<td
class="align-middle p-3 u-hide-for-mobile"
>
26 hours ago
Just now
</td>
</tr>
</tbody>
Expand Down
60 changes: 36 additions & 24 deletions assets/tests/components/detours/detourListPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,26 @@ describe("DetourListPage", () => {
Ok({
active: [
{
id: 1,
route: "1",
direction: "Inbound",
name: "Headsign A",
intersection: "Street A & Avenue B",
updatedAt: 1724866392,
details: {
id: 1,
route: "1",
direction: "Inbound",
name: "Headsign A",
intersection: "Street A & Avenue B",
updatedAt: 1724866392,
},
activatedAt: new Date(1724866392000),
},
{
id: 8,
route: "2",
direction: "Outbound",
name: "Headsign B",
intersection: "Street C & Avenue D",
updatedAt: 1724856392,
details: {
id: 8,
route: "2",
direction: "Outbound",
name: "Headsign B",
intersection: "Street C & Avenue D",
updatedAt: 1724856392,
},
activatedAt: new Date(1724856392000),
},
],
draft: undefined,
Expand Down Expand Up @@ -93,20 +99,26 @@ describe("DetourListPage", () => {
Ok({
active: [
{
id: 1,
route: "1",
direction: "Inbound",
name: "Headsign A",
intersection: "Street A & Avenue B",
updatedAt: 1724866392,
details: {
id: 1,
route: "1",
direction: "Inbound",
name: "Headsign A",
intersection: "Street A & Avenue B",
updatedAt: 1724866392,
},
activatedAt: new Date(1724866392000),
},
{
id: 8,
route: "2",
direction: "Outbound",
name: "Headsign B",
intersection: "Street C & Avenue D",
updatedAt: 1724856392,
details: {
id: 8,
route: "2",
direction: "Outbound",
name: "Headsign B",
intersection: "Street C & Avenue D",
updatedAt: 1724856392,
},
activatedAt: new Date(1724856392000),
},
],
draft: undefined,
Expand Down
10 changes: 8 additions & 2 deletions assets/tests/factories/detourListFactory.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Factory } from "fishery"
import {
ActivatedDetour,
GroupedSimpleDetours,
SimpleDetour,
} from "../../src/models/detoursList"

export const detourListFactory = Factory.define<GroupedSimpleDetours>(() => {
return {
active: [
simpleDetourFactory.build(),
simpleDetourFactory.build({ direction: "Outbound" }),
activeDetourFactory.build(),
activeDetourFactory.build({ details: { direction: "Outbound" } }),
],
draft: undefined,
past: [simpleDetourFactory.build({ name: "Headsign Z" })],
Expand All @@ -23,3 +24,8 @@ const simpleDetourFactory = Factory.define<SimpleDetour>(({ sequence }) => ({
intersection: `Street A${sequence} & Avenue B${sequence}`,
updatedAt: 1724866392,
}))

const activeDetourFactory = Factory.define<ActivatedDetour>(() => ({
activatedAt: new Date(),
details: simpleDetourFactory.build(),
}))
53 changes: 53 additions & 0 deletions lib/skate/detours/detour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,59 @@ defmodule Skate.Detours.Detour do
:author_id,
:status
]

def from(
status,
%{
state: %{
"context" => %{
"route" => %{"name" => route_name, "directionNames" => direction_names},
"routePattern" => %{"headsign" => headsign, "directionId" => direction_id},
"nearestIntersection" => nearest_intersection
}
}
} = db_detour
) do
direction = Map.get(direction_names, Integer.to_string(direction_id))

%__MODULE__{
id: db_detour.id,
route: route_name,
direction: direction,
name: headsign,
intersection: nearest_intersection,
updated_at: timestamp_to_unix(db_detour.updated_at),
author_id: db_detour.author_id,
status: status
}
end

def from(_status, _attrs), do: nil

# Converts the db timestamp to unix
defp timestamp_to_unix(db_date) do
db_date
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.to_unix()
end
end

defmodule ActivatedDetourDetails do
@moduledoc """
"""

@type t :: %__MODULE__{
activated_at: DateTime.t(),
details: Detailed.t()
}

@derive Jason.Encoder

defstruct [
:activated_at,
:details
]
end

defmodule WithState do
Expand Down
Loading

0 comments on commit 69c0258

Please sign in to comment.