refactor: reorganize code + optimization
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
7
.idea/biome.xml
generated
Normal file
7
.idea/biome.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="BiomeSettings">
|
||||||
|
<option name="enableLspFormat" value="true" />
|
||||||
|
<option name="formatOnSave" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
15
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
15
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="2">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="PyOpenGL" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="numpy" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/track-my-app.iml" filepath="$PROJECT_DIR$/.idea/track-my-app.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
12
.idea/track-my-app.iml
generated
Normal file
12
.idea/track-my-app.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
163
app/home/application-list.tsx
Normal file
163
app/home/application-list.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { createContext, memo, useContext } from "react"
|
||||||
|
import { create } from "zustand"
|
||||||
|
import { useShallow } from "zustand/react/shallow"
|
||||||
|
import { Button } from "~/components/button"
|
||||||
|
import { DEFAULT_NODE, type Entry } from "~/home/graph"
|
||||||
|
import { useStore } from "./store"
|
||||||
|
|
||||||
|
function ApplicationList() {
|
||||||
|
const entries = useStore(useShallow((state) => state.entries))
|
||||||
|
return Object.values(entries).map((entry) => (
|
||||||
|
<ApplicationListItem key={entry.name} entry={entry} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ListItemStore {
|
||||||
|
isAddingStage: boolean
|
||||||
|
newStageValue: string
|
||||||
|
|
||||||
|
setNewStageValue: (newStageValue: string) => void
|
||||||
|
setIsAddingStage: (isAddingStage: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const useListItemStore = create<ListItemStore>()((set) => ({
|
||||||
|
isAddingStage: false,
|
||||||
|
newStageValue: "",
|
||||||
|
setNewStageValue: (newStageValue: string) => set({ newStageValue }),
|
||||||
|
setIsAddingStage: (isAddingStage: boolean) => set({ isAddingStage }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const EntryContext = createContext<Entry>(null as unknown as Entry)
|
||||||
|
|
||||||
|
const ApplicationListItem = memo(({ entry }: { entry: Entry }) => (
|
||||||
|
<EntryContext value={entry}>
|
||||||
|
<details className="w-full px-2 pb-2 -mx-2">
|
||||||
|
<summary className="cursor-pointer">{entry.name}</summary>
|
||||||
|
<ol className="pl-3 list-decimal list-inside text-sm">
|
||||||
|
{entry.stages.map((step) => (
|
||||||
|
<StageItem key={step} step={step} />
|
||||||
|
))}
|
||||||
|
<NewStageInput />
|
||||||
|
</ol>
|
||||||
|
<ApplicationActions />
|
||||||
|
</details>
|
||||||
|
</EntryContext>
|
||||||
|
))
|
||||||
|
|
||||||
|
const StageItem = memo(({ step }: { step: string }) => {
|
||||||
|
const entry = useContext(EntryContext)
|
||||||
|
const deleteStageInEntry = useStore((state) => state.deleteStageInEntry)
|
||||||
|
return (
|
||||||
|
<li key={step} className="w-full group justify-between px-1">
|
||||||
|
<div className="w-[90%] inline-flex flex-row items-center justify-between">
|
||||||
|
{step}
|
||||||
|
{step !== DEFAULT_NODE.applicationSubmittedNode.key ? (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
deleteStageInEntry(step, entry.name)
|
||||||
|
}}
|
||||||
|
className="text-xs py-0 invisible group-hover:visible"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function NewStageInput() {
|
||||||
|
const isAddingStage = useListItemStore((state) => state.isAddingStage)
|
||||||
|
if (isAddingStage) {
|
||||||
|
return (
|
||||||
|
<li className="px-1">
|
||||||
|
<ActualStageInput />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
function ActualStageInput() {
|
||||||
|
const newStageValue = useListItemStore((state) => state.newStageValue)
|
||||||
|
const setNewStageValue = useListItemStore((state) => state.setNewStageValue)
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={newStageValue}
|
||||||
|
onChange={(event) => {
|
||||||
|
setNewStageValue(event.currentTarget.value)
|
||||||
|
}}
|
||||||
|
className="bg-transparent"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ApplicationActions() {
|
||||||
|
const isAddingStage = useListItemStore((state) => state.isAddingStage)
|
||||||
|
if (isAddingStage) {
|
||||||
|
return <AddStageActions />
|
||||||
|
}
|
||||||
|
return <DefaultActions />
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultActions = memo(() => {
|
||||||
|
const entry = useContext(EntryContext)
|
||||||
|
const setIsAddingStage = useListItemStore((state) => state.setIsAddingStage)
|
||||||
|
const deleteEntry = useStore((state) => state.deleteEntry)
|
||||||
|
|
||||||
|
function onDeleteApplication() {
|
||||||
|
if (confirm("Are you sure you want to delete this application?")) {
|
||||||
|
deleteEntry(entry.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row flex-wrap gap-2 pl-4 mt-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsAddingStage(true)
|
||||||
|
}}
|
||||||
|
className="text-xs py-0"
|
||||||
|
>
|
||||||
|
New stage
|
||||||
|
</Button>
|
||||||
|
<Button className="text-xs py-0">Accepted</Button>
|
||||||
|
<Button className="text-xs py-0">Rejected</Button>
|
||||||
|
<Button onClick={onDeleteApplication} className="text-xs py-0">
|
||||||
|
Delete application
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const AddStageActions = memo(() => {
|
||||||
|
const entry = useContext(EntryContext)
|
||||||
|
const setIsAddingStage = useListItemStore((state) => state.setIsAddingStage)
|
||||||
|
const setNewStageValue = useListItemStore((state) => state.setNewStageValue)
|
||||||
|
const addStageToEntry = useStore((state) => state.addStageInEntry)
|
||||||
|
|
||||||
|
function onOk() {
|
||||||
|
const stage = useListItemStore.getState().newStageValue
|
||||||
|
if (stage) {
|
||||||
|
addStageToEntry(stage, entry.name)
|
||||||
|
setIsAddingStage(false)
|
||||||
|
setNewStageValue("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancel() {
|
||||||
|
setIsAddingStage(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row space-x-2 pl-4 mt-2">
|
||||||
|
<Button onClick={onOk} className="text-xs py-0">
|
||||||
|
Ok
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel} className="text-xs py-0">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export { ApplicationList }
|
24
app/home/graph.ts
Normal file
24
app/home/graph.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
interface Node {
|
||||||
|
key: string
|
||||||
|
outs: Record<string, Connection>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Entry {
|
||||||
|
name: string
|
||||||
|
stages: Node["key"][]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Connection {
|
||||||
|
nodeKey: Node["key"]
|
||||||
|
weight: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_NODE = {
|
||||||
|
applicationSubmittedNode: {
|
||||||
|
key: "Application submitted",
|
||||||
|
outs: {},
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export { DEFAULT_NODE }
|
||||||
|
export type { Node, Entry, Connection }
|
118
app/home/store.ts
Normal file
118
app/home/store.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { create } from "zustand/index"
|
||||||
|
import { immer } from "zustand/middleware/immer"
|
||||||
|
import { DEFAULT_NODE, type Entry, type Node } from "~/home/graph"
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
nodes: Record<string, Node>
|
||||||
|
starts: Node["key"][]
|
||||||
|
entries: Record<string, Entry>
|
||||||
|
|
||||||
|
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 useStore = create<Store>()(
|
||||||
|
immer((set, get) => ({
|
||||||
|
isAddingEntry: false,
|
||||||
|
nodes: {
|
||||||
|
[DEFAULT_NODE.applicationSubmittedNode.key]:
|
||||||
|
DEFAULT_NODE.applicationSubmittedNode,
|
||||||
|
},
|
||||||
|
starts: [DEFAULT_NODE.applicationSubmittedNode.key],
|
||||||
|
entries: {},
|
||||||
|
|
||||||
|
addEntry: (name) =>
|
||||||
|
set((state) => {
|
||||||
|
const currentEntries = state.entries
|
||||||
|
if (!(name in currentEntries)) {
|
||||||
|
state.entries[name] = {
|
||||||
|
name,
|
||||||
|
stages: [DEFAULT_NODE.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 === DEFAULT_NODE.applicationSubmittedNode.key) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = state.entries[entryName]
|
||||||
|
const node = state.nodes[stage]
|
||||||
|
if (entry && node) {
|
||||||
|
const lastStageNodeKey = entry.stages.at(-2)
|
||||||
|
if (!lastStageNodeKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { useStore }
|
@@ -1,149 +1,20 @@
|
|||||||
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 clsx from "clsx"
|
||||||
|
import { useMemo, useRef, useState } from "react"
|
||||||
|
import Chart from "react-google-charts"
|
||||||
|
import { Button } from "~/components/button"
|
||||||
|
import { ApplicationList } from "~/home/application-list"
|
||||||
|
import type { Node } from "~/home/graph"
|
||||||
|
import { useStore } from "~/home/store"
|
||||||
|
import { Queue } from "~/queue"
|
||||||
import { useUiMode } from "~/use-ui-mode"
|
import { useUiMode } from "~/use-ui-mode"
|
||||||
|
|
||||||
export function meta({}: Route.MetaArgs) {
|
export function meta() {
|
||||||
return [
|
return [
|
||||||
{ title: "TrackMyApp" },
|
{ title: "TrackMyApp" },
|
||||||
{ name: "description", content: "Track and visualize your applications" },
|
{ name: "description", content: "Track and visualize your applications" },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Node {
|
|
||||||
key: string
|
|
||||||
outs: Record<string, Connection>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Entry {
|
|
||||||
name: string
|
|
||||||
stages: Node["key"][]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Connection {
|
|
||||||
nodeKey: Node["key"]
|
|
||||||
weight: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Store {
|
|
||||||
nodes: Record<string, Node>
|
|
||||||
starts: Node["key"][]
|
|
||||||
entries: Record<string, Entry>
|
|
||||||
|
|
||||||
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<Store>()(
|
|
||||||
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() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center p-4 md:px-16 md:pt-20 w-full">
|
<div className="flex items-center justify-center p-4 md:px-16 md:pt-20 w-full">
|
||||||
@@ -152,7 +23,7 @@ export default function Home() {
|
|||||||
<h1 className="text-2xl font-bold">TrackMyApp</h1>
|
<h1 className="text-2xl font-bold">TrackMyApp</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<FluffChart />
|
<FlufferChart />
|
||||||
|
|
||||||
<h2 className="text-lg font-bold my-4">My Applications</h2>
|
<h2 className="text-lg font-bold my-4">My Applications</h2>
|
||||||
|
|
||||||
@@ -163,7 +34,7 @@ export default function Home() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function FluffChart() {
|
function FlufferChart() {
|
||||||
const nodes = useStore((state) => state.nodes)
|
const nodes = useStore((state) => state.nodes)
|
||||||
const starts = useStore((state) => state.starts)
|
const starts = useStore((state) => state.starts)
|
||||||
const uiMode = useUiMode()
|
const uiMode = useUiMode()
|
||||||
@@ -213,95 +84,6 @@ function FluffChart() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ApplicationList() {
|
|
||||||
const entries = useStore(useShallow((state) => state.entries))
|
|
||||||
|
|
||||||
return Object.values(entries).map((entry) => (
|
|
||||||
<ApplicationListItem key={entry.name} entry={entry} />
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
const ApplicationListItem = memo(({ entry }: { entry: Entry }) => {
|
|
||||||
const [isAddingStage, setIsAddingStage] = useState(false)
|
|
||||||
const inputRef = useRef<HTMLInputElement | null>(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 (
|
|
||||||
<details className="w-full px-2 pb-2 -mx-2">
|
|
||||||
<summary className="cursor-pointer">{entry.name}</summary>
|
|
||||||
<ol className="pl-3 list-decimal list-inside text-sm">
|
|
||||||
{entry.stages.map((step) => (
|
|
||||||
<li key={step} className="w-full group justify-between px-1">
|
|
||||||
<div className="w-[90%] inline-flex flex-row items-center justify-between">
|
|
||||||
{step}
|
|
||||||
{step !== applicationSubmittedNode.key ? (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
deleteStageInEntry(step, entry.name)
|
|
||||||
}}
|
|
||||||
className="text-xs py-0 invisible group-hover:visible"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
{isAddingStage ? (
|
|
||||||
<li className="px-1">
|
|
||||||
<input required ref={inputRef} className="bg-transparent" />
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
</ol>
|
|
||||||
{!isAddingStage ? (
|
|
||||||
<div className="flex flex-row flex-wrap gap-2 pl-4 mt-2">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsAddingStage(true)
|
|
||||||
}}
|
|
||||||
className="text-xs py-0"
|
|
||||||
>
|
|
||||||
New stage
|
|
||||||
</Button>
|
|
||||||
<Button className="text-xs py-0">Accepted</Button>
|
|
||||||
<Button className="text-xs py-0">Rejected</Button>
|
|
||||||
<Button onClick={onDeleteApplication} className="text-xs py-0">
|
|
||||||
Delete application
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-row space-x-2 pl-4 mt-2">
|
|
||||||
<Button onClick={onOk} className="text-xs py-0">
|
|
||||||
Ok
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onCancel} className="text-xs py-0">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</details>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function AddApplicationForm() {
|
function AddApplicationForm() {
|
||||||
const [isAddingEntry, setIsAddingEntry] = useState(false)
|
const [isAddingEntry, setIsAddingEntry] = useState(false)
|
||||||
const addEntry = useStore((state) => state.addEntry)
|
const addEntry = useStore((state) => state.addEntry)
|
||||||
|
Reference in New Issue
Block a user