Skip to content

Commit c44c9ae

Browse files
authored
Merge pull request #436 from STAPLE-verse/ux-comments-final-updates
Ux comments final updates
2 parents 0a3c17a + 80cf64d commit c44c9ae

10 files changed

Lines changed: 727 additions & 18 deletions

File tree

src/core/utils/eventBus.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import mitt from "mitt"
44
type AppEvents = {
55
taskLogUpdated: void
66
announcementCreated: void
7+
milestoneTasksUpdated: void
78
// Add more events here as needed:
89
// modalClosed: void
910
// userLoggedIn: { userId: number }

src/milestones/components/MilestoneSummary.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { eventBus } from "src/core/utils/eventBus"
12
import { useQuery } from "@blitzjs/rpc"
3+
import React, { useEffect, useRef } from "react"
24
import "react-circular-progressbar/dist/styles.css"
35
import { Milestone } from "@prisma/client"
46
import { completedTaskPercentage } from "src/widgets/utils/completedTaskPercentage"
@@ -18,25 +20,20 @@ interface MilestoneSummaryProps {
1820

1921
export const MilestoneSummary: React.FC<MilestoneSummaryProps> = ({ milestone, projectId }) => {
2022
// Get tasks
21-
const [{ tasks }] = useQuery(getTasks, {
22-
include: {
23-
roles: true,
24-
},
25-
where: {
26-
projectId: projectId,
27-
milestoneId: milestone.id,
28-
},
23+
const taskQuery = useQuery(getTasks, {
24+
include: { roles: true },
25+
where: { projectId: projectId, milestoneId: milestone.id },
2926
})
27+
const [{ tasks }, { refetch: refetchTasks }] = taskQuery
3028

3129
// get taskLogs for those tasks
32-
const [fetchedTaskLogs] = useQuery(getTaskLogs, {
30+
const taskLogsQuery = useQuery(getTaskLogs, {
3331
where: {
3432
taskId: { in: tasks.map((task) => task.id) },
3533
},
36-
include: {
37-
task: true,
38-
},
39-
}) as unknown as TaskLogWithTask[]
34+
include: { task: true },
35+
})
36+
const [fetchedTaskLogs, { refetch: refetchLogs }] = taskLogsQuery as any
4037

4138
// Cast and handle the possibility of `undefined`
4239
const taskLogs: TaskLogWithTask[] = (fetchedTaskLogs ?? []) as TaskLogWithTask[]
@@ -49,6 +46,16 @@ export const MilestoneSummary: React.FC<MilestoneSummaryProps> = ({ milestone, p
4946
const taskPercent = completedTaskPercentage(tasks)
5047
const rolePercent = completedRolePercentage(tasks)
5148

49+
// Listen for custom event to refetch tasks/logs
50+
useEffect(() => {
51+
const handler = () => {
52+
void refetchTasks()
53+
void refetchLogs()
54+
}
55+
eventBus.on("milestoneTasksUpdated", handler)
56+
return () => eventBus.off("milestoneTasksUpdated", handler)
57+
}, [refetchTasks, refetchLogs])
58+
5259
return (
5360
<CollapseCard title="Milestone Statistics" className="mt-4">
5461
<div className="stats flex justify-between items-center gap-4 bg-base-300 text-lg font-bold">

src/milestones/components/UpdateTasksMilestone.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from "react"
2+
import { eventBus } from "src/core/utils/eventBus"
23
import Modal from "src/core/components/Modal"
34
import CheckboxFieldTable from "src/core/components/fields/CheckboxFieldTable"
45
import Form from "src/core/components/fields/Form"
@@ -40,6 +41,7 @@ const UpdateTasksMilestone: React.FC<UpdateTasksMilestoneProps> = ({
4041
})
4142

4243
onTasksUpdated()
44+
eventBus.emit("milestoneTasksUpdated")
4345
onClose()
4446
}
4547

src/pages/projects/[projectId]/milestones/[milestoneId].tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ const ShowMilestonePage = () => {
5252
},
5353
},
5454
})
55-
const milestoneTasks = tasks.filter((task) => task.milestoneId === null)
55+
const milestoneTasks = tasks.filter((task) => task.milestoneId === milestone.id)
56+
const updateTasks = tasks.filter(
57+
(task) => task.milestoneId === milestone.id || task.milestoneId === null
58+
)
5659
const processedTasks = processProjectTasks(milestoneTasks)
5760

5861
return (
@@ -102,7 +105,7 @@ const ShowMilestonePage = () => {
102105
open={isModalOpen}
103106
onClose={closeModal}
104107
onTasksUpdated={refetchTasks}
105-
tasks={milestoneTasks}
108+
tasks={updateTasks}
106109
/>
107110

108111
<button type="button" className="btn btn-warning" onClick={handleDelete}>

src/pages/roles/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { NewRole } from "src/roles/components/NewRole"
88
import { InformationCircleIcon } from "@heroicons/react/24/outline"
99
import { Tooltip } from "react-tooltip"
1010
import Card from "src/core/components/Card"
11+
import { DefaultRoles } from "src/roles/components/DefaultRoles"
1112

1213
const RoleBuilderPage = () => {
1314
const currentUser = useCurrentUser()
@@ -38,8 +39,9 @@ const RoleBuilderPage = () => {
3839
/>
3940
</h1>
4041

41-
<div className="flex justify-center mt-4 mb-2">
42+
<div className="flex justify-center mt-4 mb-2 gap-2">
4243
<NewRole taxonomyList={taxonomyList} onRolesChanged={refetch} />
44+
<DefaultRoles onRolesChanged={refetch} />
4345
</div>
4446
<Card title={""}>
4547
<Suspense fallback={<div>Loading...</div>}>

src/projects/components/ProjectSchemaInput.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ export const ProjectSchemaInput = ({
4444
toast.success("Default form has been successfully created!")
4545

4646
// Ensure versions exists and has at least one item
47-
const formVersionId = newFormVersion.versions?.[0]?.id
47+
const { data: updatedForms } = await refetchForms()
48+
49+
const allVersions = (updatedForms ?? []).flatMap((form) => form.formVersion ?? [])
50+
const sortedVersions = allVersions.sort(
51+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
52+
)
53+
const formVersionId = sortedVersions[0]?.id
54+
4855
if (formVersionId) {
4956
onDefaultFormCreated(formVersionId)
5057
} else {
@@ -64,7 +71,7 @@ export const ProjectSchemaInput = ({
6471
}
6572
}
6673

67-
const [userForms] = useQuery(getForms, {
74+
const [userForms, { refetch: refetchForms }] = useQuery(getForms, {
6875
where: { userId: { in: userId }, archived: false },
6976
})
7077

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useMutation } from "@blitzjs/rpc"
2+
import { useCurrentUser } from "src/users/hooks/useCurrentUser"
3+
import importDefaultRoles from "../mutations/importDefaultRoles"
4+
import { useState } from "react"
5+
import toast from "react-hot-toast"
6+
import Modal from "src/core/components/Modal"
7+
import { Form } from "react-final-form"
8+
import RadioFieldTable from "src/core/components/fields/RadioFieldTable"
9+
import { defaultRoleTemplates } from "../templates/defaultRoles"
10+
import CheckboxFieldTable from "src/core/components/fields/CheckboxFieldTable"
11+
12+
interface DefaultRolesProps {
13+
onRolesChanged?: () => void
14+
}
15+
16+
export const DefaultRoles = ({ onRolesChanged }: DefaultRolesProps) => {
17+
const currentUser = useCurrentUser()
18+
const [importDefaultRolesMutation] = useMutation(importDefaultRoles)
19+
const [openImportModal, setOpenImportModal] = useState(false)
20+
21+
const handleToggleImportModal = () => {
22+
setOpenImportModal((prev) => !prev)
23+
}
24+
25+
const options = defaultRoleTemplates.map((template, index) => ({
26+
id: index,
27+
label: template.label,
28+
}))
29+
30+
const extraData = defaultRoleTemplates.map((template) => ({
31+
link: template.link,
32+
}))
33+
34+
const extraColumns = [
35+
{
36+
id: "link",
37+
header: "Link",
38+
accessorKey: "link",
39+
cell: (info) => (
40+
<a href={info.getValue()} target="_blank" rel="noopener noreferrer" className="link">
41+
{info.getValue()}
42+
</a>
43+
),
44+
},
45+
]
46+
47+
return (
48+
<>
49+
<button type="button" className="btn btn-secondary" onClick={handleToggleImportModal}>
50+
Import Default Roles
51+
</button>
52+
53+
<Modal open={openImportModal} size="w-1/3 max-w-1/2">
54+
<Form
55+
onSubmit={async (values) => {
56+
const selectedIndex = values.defaultRoleForm?.[0]
57+
if (typeof selectedIndex !== "number") return
58+
const system = [defaultRoleTemplates[selectedIndex]!.id]
59+
try {
60+
await importDefaultRolesMutation({ system, userId: currentUser!.id })
61+
toast.success("Default roles imported!")
62+
setOpenImportModal(false)
63+
if (onRolesChanged) onRolesChanged()
64+
} catch (error: any) {
65+
toast.error("Failed to import default roles.")
66+
console.error(error)
67+
}
68+
}}
69+
render={({ handleSubmit }) => (
70+
<form onSubmit={handleSubmit}>
71+
<h1 className="mb-4 text-2xl font-bold text-center">Import Default Roles</h1>
72+
<div className="flex flex-col gap-2">
73+
<CheckboxFieldTable
74+
name="defaultRoleForm"
75+
options={options}
76+
extraColumns={extraColumns}
77+
extraData={extraData}
78+
/>
79+
</div>
80+
<div className="mt-4 flex justify-end gap-2">
81+
<button type="submit" className="btn btn-primary">
82+
Import Selected
83+
</button>
84+
<button
85+
type="button"
86+
className="btn btn-secondary"
87+
onClick={handleToggleImportModal}
88+
>
89+
Cancel
90+
</button>
91+
</div>
92+
</form>
93+
)}
94+
/>
95+
</Modal>
96+
</>
97+
)
98+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { resolver } from "@blitzjs/rpc"
2+
import db from "db"
3+
import { z } from "zod"
4+
import { defaultRoleTemplates } from "../templates/defaultRoles"
5+
6+
const roleSystemIds = defaultRoleTemplates.map((r) => r.id) as [string, ...string[]]
7+
8+
const ImportRolesSchema = z.object({
9+
system: z.array(z.enum(roleSystemIds)),
10+
userId: z.number(),
11+
})
12+
13+
export default resolver.pipe(
14+
resolver.zod(ImportRolesSchema),
15+
resolver.authorize(),
16+
async ({ system, userId }) => {
17+
// system is now an array of ids
18+
const allTemplates = system.flatMap((sysId) => {
19+
const matched = defaultRoleTemplates.find((r) => r.id === sysId)
20+
if (!matched) throw new Error(`Unknown role system: ${sysId}`)
21+
return matched.roles.map((role) => ({
22+
...role,
23+
taxonomy: matched.label,
24+
}))
25+
})
26+
27+
const createdRoles = await db.$transaction(
28+
allTemplates.map((role) =>
29+
db.role.create({
30+
data: {
31+
name: role.name,
32+
description: role.description,
33+
taxonomy: role.taxonomy,
34+
userId,
35+
},
36+
})
37+
)
38+
)
39+
40+
return createdRoles
41+
}
42+
)

src/roles/schemas.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from "zod"
2+
import { defaultRoleTemplates } from "./templates/defaultRoles"
23

34
export const CreateRoleSchema = z.object({
45
userId: z.number(),
@@ -26,3 +27,8 @@ export const RoleFormSchema = z.object({
2627
export const RoleIdsFormSchema = z.object({
2728
rolesId: z.array(z.number()).optional().nullable(),
2829
})
30+
31+
export const ImportRolesSchema = z.object({
32+
system: z.array(z.enum(defaultRoleTemplates.map((r) => r.id) as [string, ...string[]])),
33+
userId: z.number(),
34+
})

0 commit comments

Comments
 (0)