Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add activated-at column to frontend detours list #2911

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

firestack
Copy link
Member

@firestack firestack commented Dec 21, 2024

This uses the work in #2910 to fix the Activated At column to use the real activated at time, in the Activated Detours table.

Asana Ticket: https://app.asana.com/0/0/1208989312907930/f
Depends on:

@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 12381a8 to 69c0258 Compare December 21, 2024 04:52
@firestack firestack changed the title feat(ex/skate/detours): add activated_at column to detours list feat: add activated-at column to frontend detours list Dec 21, 2024
@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 69c0258 to c2eefbf Compare December 21, 2024 04:58
@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from c2eefbf to 2fa9a59 Compare January 6, 2025 21:15
@firestack firestack force-pushed the kf/asn/activated-at-column branch from ca99c97 to 67d9ff5 Compare January 6, 2025 21:40
@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 2fa9a59 to 4e9b89c Compare January 6, 2025 21:40
@firestack firestack marked this pull request as ready for review January 6, 2025 21:45
@firestack firestack requested a review from a team as a code owner January 6, 2025 21:45
Base automatically changed from kf/asn/activated-at-column to main January 8, 2025 15:59
@@ -75,12 +84,53 @@ export const DetourListPage = () => {
visibility="All Skate users"
classNames={["d-flex"]}
/>
<DetoursTable
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing DetoursTable gets rid of the empty table state, which we need in case a miracle happens and there are absolutely no active detours

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like it would be too complex to use the status === DetourStatus.Active to determine whether the extra column should be added, which should be all we need for this feature. Is there something else that was changed with the replacement?

Copy link
Member Author

@firestack firestack Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing DetoursTable gets rid of the empty table state, which we need in case a miracle happens and there are absolutely no active detours

Good catch, Thanks!

I am slightly surprised we don't have a test to have caught this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like it would be too complex to use the status === DetourStatus.Active to determine whether the extra column should be added, which should be all we need for this feature. Is there something else that was changed with the replacement?

I think doing this logic within the component would make it unreasonably messy, and would require a larger refactor to clean up the component to accept different data types, in my opinion, this way was just generally simpler and straight forward (and in my opinion points in the direction that we should not have made the table into it's own component when we did).

I tried mocking up what it would be like to do this within the existing table component to decipher which types are being used at which points, which I think introduces unnecessary conditional logic to access the types.

Commit ID: 1738660c49f3a596fd94026a3f0a3bd56f8379ed
Change ID: xpltoxzkmrnpwsqvmylwtvlskrotnluw
Author   : Kayla Firestack <[email protected]> (2025-01-09 09:36:38)
Committer: Kayla Firestack <[email protected]> (2025-01-09 10:01:41)

    (no description set)

diff --git a/assets/src/components/detourListPage.tsx b/assets/src/components/detourListPage.tsx
index 4a8a847d39..2e21a7a35e 100644
--- a/assets/src/components/detourListPage.tsx
+++ b/assets/src/components/detourListPage.tsx
@@ -84,57 +84,12 @@
             visibility="All Skate users"
             classNames={["d-flex"]}
           />
-          <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>
-                <th className="px-3 py-4 u-hide-for-mobile">Est. Duration</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>
-                      <td className="align-middle p-3 u-hide-for-mobile">
-                        {detour.estimatedDuration}
-                      </td>
-                    </tr>
-                  ))
-                : null}
-            </tbody>
-          </Table>
+          <DetoursTable
+            data={detours.active}
+            status={DetourStatus.Active}
+            onOpenDetour={onOpenDetour}
+            classNames={["mb-5"]}
+          />
           {userInTestGroup(TestGroups.DetoursPilot) && (
             <>
               <Title
diff --git a/assets/src/components/detoursTable.tsx b/assets/src/components/detoursTable.tsx
index f8fe3d9824..77af66700c 100644
--- a/assets/src/components/detoursTable.tsx
+++ b/assets/src/components/detoursTable.tsx
@@ -1,19 +1,30 @@
 import React from "react"
 import { Table } from "react-bootstrap"
 import { RoutePill } from "./routePill"
-import { useCurrentTimeSeconds } from "../hooks/useCurrentTime"
-import { timeAgoLabel } from "../util/dateTime"
-import { SimpleDetour } from "../models/detoursList"
+import useCurrentTime, { useCurrentTimeSeconds } from "../hooks/useCurrentTime"
+import { timeAgoLabel, timeAgoLabelForDates } from "../util/dateTime"
+import { ActivatedDetour, SimpleDetour } from "../models/detoursList"
 import { EmptyDetourTableIcon } from "../helpers/skateIcons"
 import { joinClasses } from "../helpers/dom"
+import { ActivateDetour } from "./detours/activateDetourModal"
 
-interface DetoursTableProps {
-  data: SimpleDetour[] | undefined
+interface DetoursTableBaseProps {
   onOpenDetour: (detourId: number) => void
-  status: DetourStatus
   classNames?: string[]
 }
 
+type DetoursTableProps = DetoursTableBaseProps &
+  (
+    | {
+        data: ActivatedDetour[] | undefined
+        status: DetourStatus.Active
+      }
+    | {
+        data: SimpleDetour[] | undefined
+        status: DetourStatus.Closed | DetourStatus.Draft
+      }
+  )
+
 export enum DetourStatus {
   Draft = "draft",
   Active = "active",
@@ -51,11 +62,26 @@
         <th className="px-3 py-4 u-hide-for-mobile">
           {timestampLabelFromStatus(status)}
         </th>
+        {status === DetourStatus.Active && (
+          <th className="px-3 py-4 u-hide-for-mobile">Est. Duration</th>
+        )}
       </tr>
     </thead>
     <tbody>
       {data ? (
-        <PopulatedDetourRows data={data} onOpenDetour={onOpenDetour} />
+        status === DetourStatus.Active ? (
+          <PopulatedDetourRows
+            status={status}
+            data={data}
+            onOpenDetour={onOpenDetour}
+          />
+        ) : (
+          <PopulatedDetourRows
+            status={status}
+            data={data}
+            onOpenDetour={onOpenDetour}
+          />
+        )
       ) : (
         <EmptyDetourRows message={`No ${status} detours.`} />
       )}
@@ -65,36 +91,67 @@
 
 const PopulatedDetourRows = ({
   data,
+  status,
   onOpenDetour,
 }: {
-  data: SimpleDetour[]
   onOpenDetour: (detourId: number) => void
-}) => {
-  const epochNowInSeconds = useCurrentTimeSeconds()
+} & (
+  | {
+      data: ActivatedDetour[]
+      status: DetourStatus.Active
+    }
+  | {
+      data: SimpleDetour[]
+      status: DetourStatus.Closed | DetourStatus.Draft
+    }
+)) => {
+  const epochNow = useCurrentTime()
+  const epochNowInSeconds = epochNow.valueOf() / 1000
 
   return (
     <>
-      {data.map((detour, index) => (
-        <tr key={index} onClick={() => onOpenDetour(detour.id)}>
-          <td className="align-middle p-3">
-            <div className="d-flex">
-              <RoutePill routeName={detour.route} />
-              <div className="c-detours-table__route-info-text d-inline-block">
-                <div className="pb-1 fs-4 fw-semibold">{detour.name}</div>
-                <div className="c-detours-table__route-info-direction fs-6">
-                  {detour.direction}
+      {data.map((detour, index) => {
+        let simpleDetour: SimpleDetour
+        if (status == DetourStatus.Active) {
+          simpleDetour = (detour as ActivatedDetour).details
+        } else {
+          simpleDetour = detour as SimpleDetour
+        }
+
+        return (
+          <tr key={index} onClick={() => onOpenDetour(simpleDetour.id)}>
+            <td className="align-middle p-3">
+              <div className="d-flex">
+                <RoutePill routeName={simpleDetour.route} />
+                <div className="c-detours-table__route-info-text d-inline-block">
+                  <div className="pb-1 fs-4 fw-semibold">
+                    {simpleDetour.name}
+                  </div>
+                  <div className="c-detours-table__route-info-direction fs-6">
+                    {simpleDetour.direction}
+                  </div>
                 </div>
               </div>
-            </div>
-          </td>
-          <td className="align-middle p-3 u-hide-for-mobile">
-            {detour.intersection}
-          </td>
-          <td className="align-middle p-3 u-hide-for-mobile">
-            {timeAgoLabel(epochNowInSeconds, detour.updatedAt)}
-          </td>
-        </tr>
-      ))}
+            </td>
+            <td className="align-middle p-3 u-hide-for-mobile">
+              {simpleDetour.intersection}
+            </td>
+            <td className="align-middle p-3 u-hide-for-mobile">
+              {status === DetourStatus.Active
+                ? timeAgoLabelForDates(
+                    (detour as ActivatedDetour).activatedAt,
+                    epochNow
+                  )
+                : timeAgoLabel(epochNowInSeconds, simpleDetour.updatedAt)}
+            </td>
+            {status === DetourStatus.Active && (
+              <td className="align-middle p-3 u-hide-for-mobile">
+                {(detour as ActivatedDetour).estimatedDuration}
+              </td>
+            )}
+          </tr>
+        )
+      })}
     </>
   )
 }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you see a better way to have done this though, I'd be happy to pair or chat about a diff or suggestion!

@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 4e9b89c to 08685fb Compare January 9, 2025 14:14
}
@spec db_detour_to_detour(status :: detour_type(), Detour.t()) :: DetailedDetour.t() | nil
def db_detour_to_detour(%{} = db_detour) do
db_detour_to_detour(categorize_detour(db_detour), db_detour)
end

def db_detour_to_detour(invalid_detour) do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has kinda been replaced by db_detour_to_detour/2 on lines 101-7, because the only reason this function would execute is if the detour is simply not a map at all (probably if it's nil?) I'm not sure this function would still be useful in that case, but if it is, then the error message should be changed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still thinking about this, but I'm going to re-request review while I consider this, to unblock the other conversations in case I can iterate on those faster.

Comment on lines +34 to +45
def from(
status,
%{
state: %{
"context" => %{
"route" => %{"name" => route_name, "directionNames" => direction_names},
"routePattern" => %{"headsign" => headsign, "directionId" => direction_id},
"nearestIntersection" => nearest_intersection
}
}
} = db_detour
) do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like moving this logic here! I'm trying to think of an alternative name but don't have a great one... Maybe an explanatory comment would be good though? I see from and assumed it was something like a kernel operation I didn't know about

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally would like to keep this convention (and do it more 😅), I did pick it up from Rust [which got inspiration from some other language IIRC] (there's a From/Into convention, where you can do

1.into<String>()
// or 
String::from(1)

and I think this is a good way to do conversions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be named from_detour_snapshot though. I think from is still the best descriptor/word though.

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

export const timeAgoLabelForDates = (start: Date, end: Date) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose / difference between this and timeAgoLabel? The input / output seems spiritually the same (even if they take different input types)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main difference is the addition of "Just now" but I made a separate function for "development speed" sake, I'll see about condensing these!

Copy link
Member Author

@firestack firestack Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little surprised by the snapshot change, so I'm gonna review that tomorrow (grr, snapshots >.>)

@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 08685fb to 700b887 Compare January 9, 2025 20:56
@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 700b887 to 220e1f9 Compare January 9, 2025 21:10
@firestack firestack force-pushed the kf/asn/actiavated-at-frontend branch from 220e1f9 to 5693457 Compare January 9, 2025 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants