import type { Route } from "./+types/home" import { create } from "zustand" import Chart from "react-google-charts" import { memo, useMemo, useRef, useState } from "react" import { Button } from "~/components/button" import { useShallow } from "zustand/react/shallow" import { immer } from "zustand/middleware/immer" import { Queue } from "~/queue" import clsx from "clsx" import { useUiMode } from "~/use-ui-mode" export function meta({}: Route.MetaArgs) { return [ { title: "TrackMyApp" }, { name: "description", content: "Track and visualize your applications" }, ] } interface Node { key: string outs: Record } interface Entry { name: string stages: Node["key"][] } interface Connection { nodeKey: Node["key"] weight: number } interface Store { nodes: Record starts: Node["key"][] entries: Record addEntry: (name: string) => void hasEntry: (name: string) => boolean addStageInEntry: (stage: string, entryName: string) => void deleteStageInEntry: (stage: string, entryName: string) => void deleteEntry: (entryName: string) => void } const applicationSubmittedNode: Node = { key: "Application submitted", outs: {}, } const useStore = create()( immer((set, get) => ({ isAddingEntry: false, nodes: { [applicationSubmittedNode.key]: applicationSubmittedNode, }, starts: [applicationSubmittedNode.key], entries: {}, addEntry: (name) => set((state) => { const currentEntries = state.entries if (!(name in currentEntries)) { state.entries[name] = { name, stages: [applicationSubmittedNode.key], } } }), hasEntry: (name) => { return name in get().entries }, addStageInEntry: (stage, entryName) => set((state) => { const entry = state.entries[entryName] if (entry) { let node = state.nodes[stage] if (!node) { node = { key: stage, outs: {}, } state.nodes[stage] = node } const lastStageNodeKey = entry.stages.at(-1) ?? state.starts[0] if (lastStageNodeKey === stage) { return } const lastStageNode = state.nodes[lastStageNodeKey] const conn = lastStageNode.outs[node.key] if (conn) { conn.weight++ } else { lastStageNode.outs[node.key] = { nodeKey: node.key, weight: 1 } } entry.stages.push(node.key) } }), deleteStageInEntry: (stage, entryName) => set((state) => { if (stage === applicationSubmittedNode.key) { return } const entry = state.entries[entryName] const node = state.nodes[stage] if (entry && node) { const lastStageNodeKey = entry.stages.at(-2)! const lastStageNode = state.nodes[lastStageNodeKey] const conn = lastStageNode.outs[node.key] if (conn) { if (conn.weight === 1) { delete lastStageNode.outs[node.key] } else { conn.weight-- } } entry.stages = entry.stages.filter((step) => step !== stage) } }), deleteEntry: (entryName) => set((state) => { const entry = state.entries[entryName] for (let i = 1; i < entry.stages.length; ++i) { const lastStageNode = state.nodes[entry.stages[i - 1]] const node = state.nodes[entry.stages[i]] const conn = lastStageNode.outs[node.key] if (conn) { if (conn.weight === 1) { delete lastStageNode.outs[node.key] } else { conn.weight-- } } } delete state.entries[entryName] }), })), ) export default function Home() { return (

TrackMyApp

My Applications

) } function FluffChart() { const nodes = useStore((state) => state.nodes) const starts = useStore((state) => state.starts) const uiMode = useUiMode() const data = useMemo(() => { const rows: [string, string, number][] = [] const queue = new Queue() for (const nodeKey of starts) { queue.enqueue(nodes[nodeKey]) } while (!queue.isEmpty) { // biome-ignore lint/style/noNonNullAssertion: if queue is non empty, then dequeue will always be non null const currentNode = queue.dequeue()! for (const nodeKey in currentNode.outs) { // biome-ignore lint/style/noNonNullAssertion: const connection = currentNode.outs[nodeKey]! rows.push([currentNode.key, connection.nodeKey, connection.weight]) queue.enqueue(nodes[connection.nodeKey]) } } return rows }, [starts, nodes]) const hasData = data.length > 0 return (
{hasData ? null : (

Enter some data to see the graph here.

)}
) } function ApplicationList() { const entries = useStore(useShallow((state) => state.entries)) return Object.values(entries).map((entry) => ( )) } const ApplicationListItem = memo(({ entry }: { entry: Entry }) => { const [isAddingStage, setIsAddingStage] = useState(false) const inputRef = useRef(null) const addStageInEntry = useStore((state) => state.addStageInEntry) const deleteStageInEntry = useStore((state) => state.deleteStageInEntry) const deleteEntry = useStore((state) => state.deleteEntry) function onOk() { if (inputRef.current && inputRef.current.value) { addStageInEntry(inputRef.current.value, entry.name) setIsAddingStage(false) } } function onCancel() { setIsAddingStage(false) } function onDeleteApplication() { if (confirm("Are you sure you want to delete this application?")) { deleteEntry(entry.name) } } return (
{entry.name}
    {entry.stages.map((step) => (
  1. {step} {step !== applicationSubmittedNode.key ? ( ) : null}
  2. ))} {isAddingStage ? (
  3. ) : null}
{!isAddingStage ? (
) : (
)}
) }) function AddApplicationForm() { const [isAddingEntry, setIsAddingEntry] = useState(false) const addEntry = useStore((state) => state.addEntry) const hasEntry = useStore((state) => state.hasEntry) const inputRef = useRef(null) function onAddButtonClick() { if (!isAddingEntry) { setIsAddingEntry(true) } else if (inputRef.current) { const entryName = inputRef.current.value if (hasEntry(entryName)) { alert(`There is already an application named ${entryName}!`) } else { addEntry(entryName) setIsAddingEntry(false) } } } function cancelEntry() { setIsAddingEntry(false) } return (
{isAddingEntry ? ( ) : null}
{isAddingEntry ? : null}
) }