repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
Projectflow
stars
latest
clone command
git clone gitlawb://did:key:z6Mkfh4Y...QBEi/projectflowgit clone gitlawb://did:key:z6Mkfh4Y.../projectflowb3cded1async from playground22h ago| #1 | import { create } from "zustand"; |
| #2 | import type { |
| #3 | User, |
| #4 | Workspace, |
| #5 | Project, |
| #6 | Issue, |
| #7 | Sprint, |
| #8 | Comment, |
| #9 | Activity, |
| #10 | Notification, |
| #11 | IssueStatus, |
| #12 | Priority, |
| #13 | IssueType, |
| #14 | SprintStatus, |
| #15 | } from "./types"; |
| #16 | import { |
| #17 | users, |
| #18 | workspaces, |
| #19 | projects, |
| #20 | issues as mockIssues, |
| #21 | sprints as mockSprints, |
| #22 | comments as mockComments, |
| #23 | activities as mockActivities, |
| #24 | notifications as mockNotifications, |
| #25 | generateId, |
| #26 | } from "./data"; |
| #27 | |
| #28 | interface AppState { |
| #29 | // Auth |
| #30 | currentUser: User | null; |
| #31 | isAuthenticated: boolean; |
| #32 | login: (email: string, password: string) => boolean; |
| #33 | register: (name: string, email: string, password: string) => boolean; |
| #34 | logout: () => void; |
| #35 | |
| #36 | // Workspace |
| #37 | currentWorkspace: Workspace | null; |
| #38 | workspaces: Workspace[]; |
| #39 | setCurrentWorkspace: (id: string) => void; |
| #40 | |
| #41 | // Projects |
| #42 | projects: Project[]; |
| #43 | currentProject: Project | null; |
| #44 | setCurrentProject: (id: string) => void; |
| #45 | createProject: (project: Omit<Project, "id">) => Project; |
| #46 | updateProject: (id: string, updates: Partial<Project>) => void; |
| #47 | deleteProject: (id: string) => void; |
| #48 | |
| #49 | // Issues |
| #50 | issues: Issue[]; |
| #51 | currentIssue: Issue | null; |
| #52 | setCurrentIssue: (issue: Issue | null) => void; |
| #53 | createIssue: ( |
| #54 | issue: Omit<Issue, "id" | "createdAt" | "updatedAt"> |
| #55 | ) => Issue; |
| #56 | updateIssue: (id: string, updates: Partial<Issue>) => void; |
| #57 | deleteIssue: (id: string) => void; |
| #58 | moveIssue: (id: string, status: IssueStatus) => void; |
| #59 | |
| #60 | // Sprints |
| #61 | sprints: Sprint[]; |
| #62 | createSprint: (sprint: Omit<Sprint, "id">) => Sprint; |
| #63 | updateSprint: (id: string, updates: Partial<Sprint>) => void; |
| #64 | deleteSprint: (id: string) => void; |
| #65 | startSprint: (id: string) => void; |
| #66 | completeSprint: (id: string) => void; |
| #67 | addIssueToSprint: (issueId: string, sprintId: string) => void; |
| #68 | removeIssueFromSprint: (issueId: string) => void; |
| #69 | |
| #70 | // Comments |
| #71 | comments: Comment[]; |
| #72 | addComment: (issueId: string, content: string) => void; |
| #73 | updateComment: (id: string, content: string) => void; |
| #74 | deleteComment: (id: string) => void; |
| #75 | |
| #76 | // Activities |
| #77 | activities: Activity[]; |
| #78 | addActivity: ( |
| #79 | activity: Omit<Activity, "id" | "createdAt"> |
| #80 | ) => void; |
| #81 | |
| #82 | // Notifications |
| #83 | notifications: Notification[]; |
| #84 | markNotificationRead: (id: string) => void; |
| #85 | markAllNotificationsRead: () => void; |
| #86 | |
| #87 | // UI State |
| #88 | sidebarCollapsed: boolean; |
| #89 | toggleSidebar: () => void; |
| #90 | searchOpen: boolean; |
| #91 | setSearchOpen: (open: boolean) => void; |
| #92 | activeView: string; |
| #93 | setActiveView: (view: string) => void; |
| #94 | |
| #95 | // Filters |
| #96 | filters: { |
| #97 | assignee: string | null; |
| #98 | priority: Priority | null; |
| #99 | status: IssueStatus | null; |
| #100 | label: string | null; |
| #101 | search: string; |
| #102 | }; |
| #103 | setFilter: ( |
| #104 | key: keyof AppState["filters"], |
| #105 | value: string | null |
| #106 | ) => void; |
| #107 | clearFilters: () => void; |
| #108 | |
| #109 | // Users |
| #110 | users: User[]; |
| #111 | getUserById: (id: string) => User | undefined; |
| #112 | } |
| #113 | |
| #114 | export const useStore = create<AppState>((set, get) => ({ |
| #115 | // Auth |
| #116 | currentUser: null, |
| #117 | isAuthenticated: false, |
| #118 | login: (email: string, _password: string) => { |
| #119 | const user = users.find((u) => u.email === email); |
| #120 | if (user) { |
| #121 | set({ currentUser: user, isAuthenticated: true }); |
| #122 | return true; |
| #123 | } |
| #124 | // Default: log in as first user for demo |
| #125 | set({ currentUser: users[0], isAuthenticated: true }); |
| #126 | return true; |
| #127 | }, |
| #128 | register: (name: string, email: string, _password: string) => { |
| #129 | const newUser: User = { |
| #130 | id: generateId(), |
| #131 | name, |
| #132 | email, |
| #133 | avatar: "", |
| #134 | createdAt: new Date().toISOString(), |
| #135 | }; |
| #136 | set({ currentUser: newUser, isAuthenticated: true }); |
| #137 | return true; |
| #138 | }, |
| #139 | logout: () => { |
| #140 | set({ currentUser: null, isAuthenticated: false }); |
| #141 | }, |
| #142 | |
| #143 | // Workspace |
| #144 | currentWorkspace: workspaces[0], |
| #145 | workspaces, |
| #146 | setCurrentWorkspace: (id: string) => { |
| #147 | const ws = workspaces.find((w) => w.id === id); |
| #148 | if (ws) set({ currentWorkspace: ws }); |
| #149 | }, |
| #150 | |
| #151 | // Projects |
| #152 | projects, |
| #153 | currentProject: projects[0], |
| #154 | setCurrentProject: (id: string) => { |
| #155 | const p = get().projects.find((proj) => proj.id === id); |
| #156 | if (p) set({ currentProject: p }); |
| #157 | }, |
| #158 | createProject: (project) => { |
| #159 | const newProject = { ...project, id: generateId() }; |
| #160 | set((s) => ({ projects: [...s.projects, newProject] })); |
| #161 | return newProject; |
| #162 | }, |
| #163 | updateProject: (id, updates) => { |
| #164 | set((s) => ({ |
| #165 | projects: s.projects.map((p) => |
| #166 | p.id === id ? { ...p, ...updates } : p |
| #167 | ), |
| #168 | currentProject: |
| #169 | s.currentProject?.id === id |
| #170 | ? { ...s.currentProject, ...updates } |
| #171 | : s.currentProject, |
| #172 | })); |
| #173 | }, |
| #174 | deleteProject: (id) => { |
| #175 | set((s) => ({ |
| #176 | projects: s.projects.filter((p) => p.id !== id), |
| #177 | issues: s.issues.filter((i) => i.projectId !== id), |
| #178 | sprints: s.sprints.filter((sp) => sp.projectId !== id), |
| #179 | currentProject: |
| #180 | s.currentProject?.id === id ? s.projects[0] : s.currentProject, |
| #181 | })); |
| #182 | }, |
| #183 | |
| #184 | // Issues |
| #185 | issues: mockIssues, |
| #186 | currentIssue: null, |
| #187 | setCurrentIssue: (issue) => set({ currentIssue: issue }), |
| #188 | createIssue: (issueData) => { |
| #189 | const project = get().projects.find( |
| #190 | (p) => p.id === issueData.projectId |
| #191 | ); |
| #192 | const projectIssues = get().issues.filter( |
| #193 | (i) => i.projectId === issueData.projectId |
| #194 | ); |
| #195 | const prefix = project |
| #196 | ? project.name |
| #197 | .split(" ") |
| #198 | .map((w) => w[0]) |
| #199 | .join("") |
| #200 | .toUpperCase() |
| #201 | .slice(0, 3) |
| #202 | : "ISS"; |
| #203 | const newIssue: Issue = { |
| #204 | ...issueData, |
| #205 | id: generateId(), |
| #206 | identifier: `${prefix}-${projectIssues.length + 1}`, |
| #207 | createdAt: new Date().toISOString(), |
| #208 | updatedAt: new Date().toISOString(), |
| #209 | }; |
| #210 | set((s) => ({ issues: [...s.issues, newIssue] })); |
| #211 | get().addActivity({ |
| #212 | issueId: newIssue.id, |
| #213 | userId: get().currentUser?.id || "u1", |
| #214 | type: "created", |
| #215 | }); |
| #216 | return newIssue; |
| #217 | }, |
| #218 | updateIssue: (id, updates) => { |
| #219 | const oldIssue = get().issues.find((i) => i.id === id); |
| #220 | set((s) => ({ |
| #221 | issues: s.issues.map((i) => |
| #222 | i.id === id |
| #223 | ? { ...i, ...updates, updatedAt: new Date().toISOString() } |
| #224 | : i |
| #225 | ), |
| #226 | currentIssue: |
| #227 | s.currentIssue?.id === id |
| #228 | ? { |
| #229 | ...s.currentIssue, |
| #230 | ...updates, |
| #231 | updatedAt: new Date().toISOString(), |
| #232 | } |
| #233 | : s.currentIssue, |
| #234 | })); |
| #235 | // Track status changes |
| #236 | if (oldIssue && updates.status && updates.status !== oldIssue.status) { |
| #237 | get().addActivity({ |
| #238 | issueId: id, |
| #239 | userId: get().currentUser?.id || "u1", |
| #240 | type: "status_change", |
| #241 | oldValue: oldIssue.status, |
| #242 | newValue: updates.status, |
| #243 | }); |
| #244 | } |
| #245 | // Track assignment changes |
| #246 | if ( |
| #247 | oldIssue && |
| #248 | updates.assigneeId !== undefined && |
| #249 | updates.assigneeId !== oldIssue.assigneeId |
| #250 | ) { |
| #251 | get().addActivity({ |
| #252 | issueId: id, |
| #253 | userId: get().currentUser?.id || "u1", |
| #254 | type: "assignment", |
| #255 | newValue: updates.assigneeId || undefined, |
| #256 | }); |
| #257 | } |
| #258 | }, |
| #259 | deleteIssue: (id) => { |
| #260 | set((s) => ({ |
| #261 | issues: s.issues.filter((i) => i.id !== id), |
| #262 | currentIssue: s.currentIssue?.id === id ? null : s.currentIssue, |
| #263 | comments: s.comments.filter((c) => c.issueId !== id), |
| #264 | activities: s.activities.filter((a) => a.issueId !== id), |
| #265 | })); |
| #266 | }, |
| #267 | moveIssue: (id, status) => { |
| #268 | get().updateIssue(id, { status }); |
| #269 | }, |
| #270 | |
| #271 | // Sprints |
| #272 | sprints: mockSprints, |
| #273 | createSprint: (sprintData) => { |
| #274 | const newSprint = { ...sprintData, id: generateId() }; |
| #275 | set((s) => ({ sprints: [...s.sprints, newSprint] })); |
| #276 | return newSprint; |
| #277 | }, |
| #278 | updateSprint: (id, updates) => { |
| #279 | set((s) => ({ |
| #280 | sprints: s.sprints.map((sp) => |
| #281 | sp.id === id ? { ...sp, ...updates } : sp |
| #282 | ), |
| #283 | })); |
| #284 | }, |
| #285 | deleteSprint: (id) => { |
| #286 | set((s) => ({ |
| #287 | sprints: s.sprints.filter((sp) => sp.id !== id), |
| #288 | issues: s.issues.map((i) => |
| #289 | i.sprintId === id ? { ...i, sprintId: null } : i |
| #290 | ), |
| #291 | })); |
| #292 | }, |
| #293 | startSprint: (id) => { |
| #294 | const sprint = get().sprints.find((s) => s.id === id); |
| #295 | if (sprint) { |
| #296 | get().updateSprint(id, { status: "active" }); |
| #297 | // Create notification |
| #298 | const sprintIssues = get().issues.filter((i) => i.sprintId === id); |
| #299 | const notification: Notification = { |
| #300 | id: generateId(), |
| #301 | userId: get().currentUser?.id || "u1", |
| #302 | type: "sprint_started", |
| #303 | title: "Sprint started", |
| #304 | message: `${sprint.name} has started with ${sprintIssues.length} issues`, |
| #305 | read: false, |
| #306 | createdAt: new Date().toISOString(), |
| #307 | }; |
| #308 | set((s) => ({ |
| #309 | notifications: [notification, ...s.notifications], |
| #310 | })); |
| #311 | } |
| #312 | }, |
| #313 | completeSprint: (id) => { |
| #314 | get().updateSprint(id, { status: "completed" }); |
| #315 | }, |
| #316 | addIssueToSprint: (issueId, sprintId) => { |
| #317 | get().updateIssue(issueId, { sprintId }); |
| #318 | }, |
| #319 | removeIssueFromSprint: (issueId) => { |
| #320 | get().updateIssue(issueId, { sprintId: null, status: "backlog" }); |
| #321 | }, |
| #322 | |
| #323 | // Comments |
| #324 | comments: mockComments, |
| #325 | addComment: (issueId, content) => { |
| #326 | const newComment: Comment = { |
| #327 | id: generateId(), |
| #328 | issueId, |
| #329 | userId: get().currentUser?.id || "u1", |
| #330 | content, |
| #331 | createdAt: new Date().toISOString(), |
| #332 | updatedAt: new Date().toISOString(), |
| #333 | }; |
| #334 | set((s) => ({ comments: [...s.comments, newComment] })); |
| #335 | }, |
| #336 | updateComment: (id, content) => { |
| #337 | set((s) => ({ |
| #338 | comments: s.comments.map((c) => |
| #339 | c.id === id |
| #340 | ? { ...c, content, updatedAt: new Date().toISOString() } |
| #341 | : c |
| #342 | ), |
| #343 | })); |
| #344 | }, |
| #345 | deleteComment: (id) => { |
| #346 | set((s) => ({ |
| #347 | comments: s.comments.filter((c) => c.id !== id), |
| #348 | })); |
| #349 | }, |
| #350 | |
| #351 | // Activities |
| #352 | activities: mockActivities, |
| #353 | addActivity: (activityData) => { |
| #354 | const activity: Activity = { |
| #355 | ...activityData, |
| #356 | id: generateId(), |
| #357 | createdAt: new Date().toISOString(), |
| #358 | }; |
| #359 | set((s) => ({ activities: [activity, ...s.activities] })); |
| #360 | }, |
| #361 | |
| #362 | // Notifications |
| #363 | notifications: mockNotifications, |
| #364 | markNotificationRead: (id) => { |
| #365 | set((s) => ({ |
| #366 | notifications: s.notifications.map((n) => |
| #367 | n.id === id ? { ...n, read: true } : n |
| #368 | ), |
| #369 | })); |
| #370 | }, |
| #371 | markAllNotificationsRead: () => { |
| #372 | set((s) => ({ |
| #373 | notifications: s.notifications.map((n) => ({ ...n, read: true })), |
| #374 | })); |
| #375 | }, |
| #376 | |
| #377 | // UI State |
| #378 | sidebarCollapsed: false, |
| #379 | toggleSidebar: () => |
| #380 | set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), |
| #381 | searchOpen: false, |
| #382 | setSearchOpen: (open) => set({ searchOpen: open }), |
| #383 | activeView: "dashboard", |
| #384 | setActiveView: (view) => set({ activeView: view }), |
| #385 | |
| #386 | // Filters |
| #387 | filters: { |
| #388 | assignee: null, |
| #389 | priority: null, |
| #390 | status: null, |
| #391 | label: null, |
| #392 | search: "", |
| #393 | }, |
| #394 | setFilter: (key, value) => |
| #395 | set((s) => ({ |
| #396 | filters: { ...s.filters, [key]: value }, |
| #397 | })), |
| #398 | clearFilters: () => |
| #399 | set({ |
| #400 | filters: { |
| #401 | assignee: null, |
| #402 | priority: null, |
| #403 | status: null, |
| #404 | label: null, |
| #405 | search: "", |
| #406 | }, |
| #407 | }), |
| #408 | |
| #409 | // Users |
| #410 | users, |
| #411 | getUserById: (id) => get().users.find((u) => u.id === id), |
| #412 | })); |
| #413 |