rusty-replay 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +10 -0
- package/.vscode/settings.json +3 -0
- package/README.md +92 -0
- package/apps/web/README.md +11 -0
- package/apps/web/api/auth/keys.ts +3 -0
- package/apps/web/api/auth/types.ts +25 -0
- package/apps/web/api/auth/use-query-profile.ts +19 -0
- package/apps/web/api/auth/use-sign-in.ts +24 -0
- package/apps/web/api/axios.ts +122 -0
- package/apps/web/api/error-code.ts +36 -0
- package/apps/web/api/event/keys.ts +14 -0
- package/apps/web/api/event/types.ts +91 -0
- package/apps/web/api/event/use-mutation-event-assignee.ts +103 -0
- package/apps/web/api/event/use-mutation-event-priority.ts +97 -0
- package/apps/web/api/event/use-mutation-event-status.ts +198 -0
- package/apps/web/api/event/use-query-event-detail.ts +25 -0
- package/apps/web/api/event/use-query-event-list.ts +42 -0
- package/apps/web/api/health-check/index.ts +21 -0
- package/apps/web/api/project/keys.ts +4 -0
- package/apps/web/api/project/types.ts +28 -0
- package/apps/web/api/project/use-create-project.ts +30 -0
- package/apps/web/api/project/use-query-project-list.ts +19 -0
- package/apps/web/api/project/use-query-project-users.ts +23 -0
- package/apps/web/api/types.ts +44 -0
- package/apps/web/app/(auth)/layout.tsx +5 -0
- package/apps/web/app/(auth)/sign-in/page.tsx +20 -0
- package/apps/web/app/(auth)/sign-up/page.tsx +5 -0
- package/apps/web/app/(project)/project/[project_id]/issues/[issue_id]/page.tsx +17 -0
- package/apps/web/app/(project)/project/[project_id]/issues/page.tsx +15 -0
- package/apps/web/app/(project)/project/[project_id]/page.tsx +10 -0
- package/apps/web/app/(project)/project/page.tsx +10 -0
- package/apps/web/app/(report)/error-list/page.tsx +7 -0
- package/apps/web/app/favicon.ico +0 -0
- package/apps/web/app/layout.tsx +35 -0
- package/apps/web/app/page.tsx +3 -0
- package/apps/web/components/.gitkeep +0 -0
- package/apps/web/components/event-list/event-detail.tsx +242 -0
- package/apps/web/components/event-list/event-list.tsx +376 -0
- package/apps/web/components/event-list/preview.tsx +573 -0
- package/apps/web/components/layouts/default-layout.tsx +59 -0
- package/apps/web/components/login-form.tsx +124 -0
- package/apps/web/components/project/create-project.tsx +130 -0
- package/apps/web/components/project/hooks/use-get-event-params.ts +9 -0
- package/apps/web/components/project/hooks/use-get-project-params.ts +10 -0
- package/apps/web/components/project/project-detail.tsx +240 -0
- package/apps/web/components/project/project-list.tsx +137 -0
- package/apps/web/components/providers.tsx +25 -0
- package/apps/web/components/ui/assignee-dropdown.tsx +176 -0
- package/apps/web/components/ui/event-status-dropdown.tsx +104 -0
- package/apps/web/components/ui/priority-dropdown.tsx +123 -0
- package/apps/web/components/widget/app-sidebar.tsx +225 -0
- package/apps/web/components/widget/nav-main.tsx +73 -0
- package/apps/web/components/widget/nav-projects.tsx +84 -0
- package/apps/web/components/widget/nav-user.tsx +113 -0
- package/apps/web/components.json +20 -0
- package/apps/web/constants/routes.ts +12 -0
- package/apps/web/eslint.config.js +4 -0
- package/apps/web/hooks/use-boolean-state.ts +13 -0
- package/apps/web/lib/.gitkeep +0 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.mjs +6 -0
- package/apps/web/package.json +60 -0
- package/apps/web/postcss.config.mjs +1 -0
- package/apps/web/providers/flag-provider.tsx +35 -0
- package/apps/web/providers/query-client-provider.tsx +17 -0
- package/apps/web/providers/telemetry-provider.tsx +12 -0
- package/apps/web/tsconfig.json +24 -0
- package/apps/web/utils/avatar.ts +26 -0
- package/apps/web/utils/date.ts +26 -0
- package/apps/web/utils/front-end-tracer.ts +119 -0
- package/apps/web/utils/schema/project.schema.ts +12 -0
- package/apps/web/utils/span-processor.ts +36 -0
- package/package.json +21 -0
- package/packages/eslint-config/README.md +3 -0
- package/packages/eslint-config/base.js +32 -0
- package/packages/eslint-config/next.js +51 -0
- package/packages/eslint-config/package.json +25 -0
- package/packages/eslint-config/react-internal.js +41 -0
- package/packages/rusty-replay/README.md +165 -0
- package/packages/rusty-replay/package.json +67 -0
- package/packages/rusty-replay/src/environment.ts +27 -0
- package/packages/rusty-replay/src/error-batcher.ts +75 -0
- package/packages/rusty-replay/src/front-end-tracer.ts +86 -0
- package/packages/rusty-replay/src/handler.ts +37 -0
- package/packages/rusty-replay/src/index.ts +8 -0
- package/packages/rusty-replay/src/recorder.ts +71 -0
- package/packages/rusty-replay/src/reporter.ts +115 -0
- package/packages/rusty-replay/src/utils.ts +13 -0
- package/packages/rusty-replay/tsconfig.build.json +13 -0
- package/packages/rusty-replay/tsconfig.json +27 -0
- package/packages/rusty-replay/tsup.config.ts +39 -0
- package/packages/typescript-config/README.md +3 -0
- package/packages/typescript-config/base.json +20 -0
- package/packages/typescript-config/nextjs.json +13 -0
- package/packages/typescript-config/package.json +9 -0
- package/packages/typescript-config/react-library.json +8 -0
- package/packages/ui/components.json +20 -0
- package/packages/ui/eslint.config.js +4 -0
- package/packages/ui/package.json +60 -0
- package/packages/ui/postcss.config.mjs +6 -0
- package/packages/ui/src/components/.gitkeep +0 -0
- package/packages/ui/src/components/avatar.tsx +53 -0
- package/packages/ui/src/components/badge.tsx +46 -0
- package/packages/ui/src/components/breadcrumb.tsx +109 -0
- package/packages/ui/src/components/button.tsx +59 -0
- package/packages/ui/src/components/calendar.tsx +75 -0
- package/packages/ui/src/components/calendars/date-picker.tsx +43 -0
- package/packages/ui/src/components/calendars/date-range-picker.tsx +79 -0
- package/packages/ui/src/components/card.tsx +92 -0
- package/packages/ui/src/components/checkbox.tsx +32 -0
- package/packages/ui/src/components/collapsible.tsx +33 -0
- package/packages/ui/src/components/dialog.tsx +135 -0
- package/packages/ui/src/components/dialogs/confirmation-modal.tsx +216 -0
- package/packages/ui/src/components/dropdown-menu.tsx +261 -0
- package/packages/ui/src/components/input.tsx +30 -0
- package/packages/ui/src/components/label.tsx +24 -0
- package/packages/ui/src/components/login-form.tsx +68 -0
- package/packages/ui/src/components/mode-switcher.tsx +34 -0
- package/packages/ui/src/components/popover.tsx +48 -0
- package/packages/ui/src/components/scroll-area.tsx +58 -0
- package/packages/ui/src/components/select.tsx +185 -0
- package/packages/ui/src/components/separator.tsx +28 -0
- package/packages/ui/src/components/sheet.tsx +139 -0
- package/packages/ui/src/components/sidebar.tsx +726 -0
- package/packages/ui/src/components/skeleton.tsx +13 -0
- package/packages/ui/src/components/sonner.tsx +25 -0
- package/packages/ui/src/components/table.tsx +116 -0
- package/packages/ui/src/components/tabs.tsx +66 -0
- package/packages/ui/src/components/team-switcher.tsx +91 -0
- package/packages/ui/src/components/textarea.tsx +18 -0
- package/packages/ui/src/components/tooltip.tsx +61 -0
- package/packages/ui/src/hooks/.gitkeep +0 -0
- package/packages/ui/src/hooks/use-meta-color.ts +28 -0
- package/packages/ui/src/hooks/use-mobile.ts +19 -0
- package/packages/ui/src/lib/utils.ts +6 -0
- package/packages/ui/src/styles/globals.css +138 -0
- package/packages/ui/tsconfig.json +13 -0
- package/packages/ui/tsconfig.lint.json +8 -0
- package/pnpm-workspace.yaml +4 -0
- package/tsconfig.json +4 -0
- package/turbo.json +21 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
import { useState } from 'react';
|
2
|
+
import {
|
3
|
+
DropdownMenu,
|
4
|
+
DropdownMenuCheckboxItem,
|
5
|
+
DropdownMenuCheckboxItemProps,
|
6
|
+
DropdownMenuContent,
|
7
|
+
DropdownMenuLabel,
|
8
|
+
DropdownMenuSeparator,
|
9
|
+
DropdownMenuTrigger,
|
10
|
+
DropdownMenuItem,
|
11
|
+
} from '@workspace/ui/components/dropdown-menu';
|
12
|
+
import { ChevronDown, Search, Plus, UserCircle } from 'lucide-react';
|
13
|
+
import { Badge } from '@workspace/ui/components/badge';
|
14
|
+
import { toast } from '@workspace/ui/components/sonner';
|
15
|
+
import { useMutationEventAssignee } from '@/api/event/use-mutation-event-assignee';
|
16
|
+
import { ProjectMemberResponse } from '@/api/project/types';
|
17
|
+
import { Input } from '@workspace/ui/components/input';
|
18
|
+
import { getAvatarColor, getInitials } from '@/utils/avatar';
|
19
|
+
|
20
|
+
type Checked = DropdownMenuCheckboxItemProps['checked'];
|
21
|
+
|
22
|
+
interface Props {
|
23
|
+
projectId: number | undefined;
|
24
|
+
eventId: number;
|
25
|
+
userList: ProjectMemberResponse[] | undefined;
|
26
|
+
currentAssigneeId: number | null;
|
27
|
+
}
|
28
|
+
|
29
|
+
export function AssigneeDropdown({
|
30
|
+
projectId,
|
31
|
+
eventId,
|
32
|
+
userList,
|
33
|
+
currentAssigneeId,
|
34
|
+
}: Props) {
|
35
|
+
const [searchTerm, setSearchTerm] = useState<string>('');
|
36
|
+
|
37
|
+
const { mutateAsync: mutateAssign, isPending: isMutatingAssign } =
|
38
|
+
useMutationEventAssignee({
|
39
|
+
projectId: projectId!,
|
40
|
+
eventId,
|
41
|
+
});
|
42
|
+
|
43
|
+
const selectedUser = userList?.find(
|
44
|
+
(user) => user.userId === currentAssigneeId
|
45
|
+
);
|
46
|
+
|
47
|
+
const filteredUsers = userList?.filter(
|
48
|
+
(user) =>
|
49
|
+
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
50
|
+
user.email.toLowerCase().includes(searchTerm.toLowerCase())
|
51
|
+
);
|
52
|
+
|
53
|
+
const handleAssigneeChange = async (userId: number) => {
|
54
|
+
try {
|
55
|
+
const isCurrentlySelected = currentAssigneeId === userId;
|
56
|
+
const newAssigneeId = isCurrentlySelected ? null : userId;
|
57
|
+
|
58
|
+
await mutateAssign(
|
59
|
+
{ assignedTo: newAssigneeId, eventIds: [eventId] },
|
60
|
+
{
|
61
|
+
onSuccess: () => {
|
62
|
+
toast.success(
|
63
|
+
isCurrentlySelected
|
64
|
+
? '담당자가 해제되었습니다!'
|
65
|
+
: '담당자가 지정되었습니다!'
|
66
|
+
);
|
67
|
+
},
|
68
|
+
onError: (error) => {
|
69
|
+
console.error('Error updating assignee:', error);
|
70
|
+
toast.error('담당자 변경에 실패했습니다.');
|
71
|
+
},
|
72
|
+
}
|
73
|
+
);
|
74
|
+
} catch (error) {
|
75
|
+
console.error('Error:', error);
|
76
|
+
}
|
77
|
+
};
|
78
|
+
|
79
|
+
return (
|
80
|
+
<DropdownMenu>
|
81
|
+
<DropdownMenuTrigger asChild>
|
82
|
+
<Badge
|
83
|
+
variant="secondary"
|
84
|
+
className="flex items-center gap-1 cursor-pointer"
|
85
|
+
>
|
86
|
+
{selectedUser ? (
|
87
|
+
<>
|
88
|
+
<div
|
89
|
+
className={`w-5 h-5 rounded-full flex items-center justify-center text-xs text-white ${getAvatarColor(selectedUser.username)}`}
|
90
|
+
>
|
91
|
+
{getInitials(selectedUser.username)}
|
92
|
+
</div>
|
93
|
+
{selectedUser.username}
|
94
|
+
</>
|
95
|
+
) : (
|
96
|
+
<>
|
97
|
+
<UserCircle className="h-4 w-4" />
|
98
|
+
담당자
|
99
|
+
</>
|
100
|
+
)}
|
101
|
+
<ChevronDown className="h-3 w-3 ml-1" />
|
102
|
+
</Badge>
|
103
|
+
</DropdownMenuTrigger>
|
104
|
+
<DropdownMenuContent className="w-72">
|
105
|
+
<DropdownMenuLabel className="font-bold text-lg">
|
106
|
+
담당자
|
107
|
+
</DropdownMenuLabel>
|
108
|
+
|
109
|
+
<div className="px-2 py-2">
|
110
|
+
<div className="relative">
|
111
|
+
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
112
|
+
<Input
|
113
|
+
placeholder="사용자 또는 팀 검색..."
|
114
|
+
className="pl-8"
|
115
|
+
value={searchTerm}
|
116
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
117
|
+
/>
|
118
|
+
</div>
|
119
|
+
</div>
|
120
|
+
|
121
|
+
<DropdownMenuSeparator />
|
122
|
+
|
123
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground uppercase">
|
124
|
+
멤버
|
125
|
+
</DropdownMenuLabel>
|
126
|
+
|
127
|
+
{filteredUsers?.map((user) => (
|
128
|
+
<DropdownMenuCheckboxItem
|
129
|
+
key={user.userId}
|
130
|
+
checked={currentAssigneeId === user.userId}
|
131
|
+
onCheckedChange={() => handleAssigneeChange(user.userId)}
|
132
|
+
className="flex items-center gap-2 py-2"
|
133
|
+
disabled={isMutatingAssign}
|
134
|
+
>
|
135
|
+
<div
|
136
|
+
className={`w-8 h-8 rounded-full flex items-center justify-center text-white text-sm ${getAvatarColor(user.username)}`}
|
137
|
+
>
|
138
|
+
{getInitials(user.username)}
|
139
|
+
</div>
|
140
|
+
<div className="flex flex-col">
|
141
|
+
<span className="font-medium">{user.username}</span>
|
142
|
+
<span className="text-xs text-muted-foreground">
|
143
|
+
{user.email}
|
144
|
+
</span>
|
145
|
+
</div>
|
146
|
+
</DropdownMenuCheckboxItem>
|
147
|
+
))}
|
148
|
+
|
149
|
+
<DropdownMenuSeparator />
|
150
|
+
|
151
|
+
<DropdownMenuLabel className="text-xs text-muted-foreground uppercase">
|
152
|
+
팀
|
153
|
+
</DropdownMenuLabel>
|
154
|
+
|
155
|
+
<DropdownMenuCheckboxItem
|
156
|
+
checked={false}
|
157
|
+
onCheckedChange={() => {}}
|
158
|
+
className="flex items-center gap-2 py-2"
|
159
|
+
disabled={true}
|
160
|
+
>
|
161
|
+
<div className="w-8 h-8 rounded-full flex items-center justify-center bg-pink-500 text-white text-sm">
|
162
|
+
R
|
163
|
+
</div>
|
164
|
+
<span className="font-medium">#reable</span>
|
165
|
+
</DropdownMenuCheckboxItem>
|
166
|
+
|
167
|
+
<DropdownMenuSeparator />
|
168
|
+
|
169
|
+
<DropdownMenuItem className="flex items-center gap-2 py-2">
|
170
|
+
<Plus className="h-4 w-4" />
|
171
|
+
<span>멤버 초대</span>
|
172
|
+
</DropdownMenuItem>
|
173
|
+
</DropdownMenuContent>
|
174
|
+
</DropdownMenu>
|
175
|
+
);
|
176
|
+
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
'use client';
|
2
|
+
import { useState } from 'react';
|
3
|
+
import {
|
4
|
+
DropdownMenu,
|
5
|
+
DropdownMenuCheckboxItem,
|
6
|
+
DropdownMenuCheckboxItemProps,
|
7
|
+
DropdownMenuContent,
|
8
|
+
DropdownMenuLabel,
|
9
|
+
DropdownMenuSeparator,
|
10
|
+
DropdownMenuTrigger,
|
11
|
+
} from '@workspace/ui/components/dropdown-menu';
|
12
|
+
import { CheckCircle, XCircle, ChevronDown } from 'lucide-react';
|
13
|
+
import { Badge } from '@workspace/ui/components/badge';
|
14
|
+
import { EventStatusType } from '@/api/event/types';
|
15
|
+
import { useMutationEventStatus } from '@/api/event/use-mutation-event-status';
|
16
|
+
import { toast } from '@workspace/ui/components/sonner';
|
17
|
+
|
18
|
+
type Checked = DropdownMenuCheckboxItemProps['checked'];
|
19
|
+
|
20
|
+
interface Props {
|
21
|
+
status: EventStatusType | null;
|
22
|
+
projectId: number | undefined;
|
23
|
+
eventId: number;
|
24
|
+
}
|
25
|
+
|
26
|
+
const StatusIcon = ({ status }: { status: EventStatusType | null }) => {
|
27
|
+
switch (status) {
|
28
|
+
case 'RESOLVED':
|
29
|
+
return <CheckCircle className="h-4 w-4" />;
|
30
|
+
case 'UNRESOLVED':
|
31
|
+
return <XCircle className="h-4 w-4" />;
|
32
|
+
default:
|
33
|
+
return null;
|
34
|
+
}
|
35
|
+
};
|
36
|
+
|
37
|
+
export function EventStatusDropdown({ status, projectId, eventId }: Props) {
|
38
|
+
const [statusResolved, setStatusResolved] = useState<Checked>(
|
39
|
+
status === 'RESOLVED'
|
40
|
+
);
|
41
|
+
const [statusUnresolved, setStatusUnresolved] = useState<Checked>(
|
42
|
+
status === 'UNRESOLVED'
|
43
|
+
);
|
44
|
+
|
45
|
+
const { mutateAsync: mutateStatus, isPending: isMutatingStatus } =
|
46
|
+
useMutationEventStatus({ projectId: projectId! });
|
47
|
+
|
48
|
+
const handleStatusChange = async (status: EventStatusType) => {
|
49
|
+
await mutateStatus(
|
50
|
+
{ status, eventIds: [eventId] },
|
51
|
+
{
|
52
|
+
onSuccess: () => {
|
53
|
+
setStatusResolved(status === 'RESOLVED');
|
54
|
+
setStatusUnresolved(status === 'UNRESOLVED');
|
55
|
+
toast.success('Status updated successfully!');
|
56
|
+
},
|
57
|
+
onError: (error) => {
|
58
|
+
console.error('Error updating status:', error);
|
59
|
+
toast.error('Failed to update status.');
|
60
|
+
},
|
61
|
+
}
|
62
|
+
);
|
63
|
+
};
|
64
|
+
|
65
|
+
return (
|
66
|
+
<DropdownMenu>
|
67
|
+
<DropdownMenuTrigger asChild>
|
68
|
+
<Badge
|
69
|
+
variant="secondary"
|
70
|
+
className="flex items-center gap-1 cursor-pointer"
|
71
|
+
>
|
72
|
+
<StatusIcon status={status} />
|
73
|
+
{status === 'RESOLVED' && 'Resolved'}
|
74
|
+
{status === 'UNRESOLVED' && 'Unresolved'}
|
75
|
+
<ChevronDown />
|
76
|
+
</Badge>
|
77
|
+
</DropdownMenuTrigger>
|
78
|
+
<DropdownMenuContent className="w-56">
|
79
|
+
<DropdownMenuLabel>Set Status</DropdownMenuLabel>
|
80
|
+
<DropdownMenuSeparator />
|
81
|
+
<DropdownMenuCheckboxItem
|
82
|
+
checked={statusResolved}
|
83
|
+
onCheckedChange={() => handleStatusChange('RESOLVED')}
|
84
|
+
className="flex items-center gap-2"
|
85
|
+
>
|
86
|
+
<Badge variant={'secondary'}>
|
87
|
+
<CheckCircle className="h-4 w-4" />
|
88
|
+
Resolved
|
89
|
+
</Badge>
|
90
|
+
</DropdownMenuCheckboxItem>
|
91
|
+
<DropdownMenuCheckboxItem
|
92
|
+
checked={statusUnresolved}
|
93
|
+
onCheckedChange={() => handleStatusChange('UNRESOLVED')}
|
94
|
+
className="flex items-center gap-2"
|
95
|
+
>
|
96
|
+
<Badge variant={'secondary'}>
|
97
|
+
<XCircle className="h-4 w-4" />
|
98
|
+
Unresolved
|
99
|
+
</Badge>
|
100
|
+
</DropdownMenuCheckboxItem>
|
101
|
+
</DropdownMenuContent>
|
102
|
+
</DropdownMenu>
|
103
|
+
);
|
104
|
+
}
|
@@ -0,0 +1,123 @@
|
|
1
|
+
'use client';
|
2
|
+
import { useState } from 'react';
|
3
|
+
import {
|
4
|
+
DropdownMenu,
|
5
|
+
DropdownMenuCheckboxItem,
|
6
|
+
DropdownMenuCheckboxItemProps,
|
7
|
+
DropdownMenuContent,
|
8
|
+
DropdownMenuLabel,
|
9
|
+
DropdownMenuSeparator,
|
10
|
+
DropdownMenuTrigger,
|
11
|
+
} from '@workspace/ui/components/dropdown-menu';
|
12
|
+
import {
|
13
|
+
SignalHigh,
|
14
|
+
SignalMedium,
|
15
|
+
SignalLow,
|
16
|
+
ArrowDown,
|
17
|
+
ChevronDown,
|
18
|
+
} from 'lucide-react';
|
19
|
+
import { Badge } from '@workspace/ui/components/badge';
|
20
|
+
import { EventPriorityType } from '@/api/event/types';
|
21
|
+
import { useMutationEventPriority } from '@/api/event/use-mutation-event-priority';
|
22
|
+
import { toast } from '@workspace/ui/components/sonner';
|
23
|
+
|
24
|
+
type Checked = DropdownMenuCheckboxItemProps['checked'];
|
25
|
+
|
26
|
+
interface Props {
|
27
|
+
priority: EventPriorityType | null;
|
28
|
+
projectId: number | undefined;
|
29
|
+
eventId: number;
|
30
|
+
}
|
31
|
+
|
32
|
+
const PriorityIcon = ({ priority }: { priority: EventPriorityType | null }) => {
|
33
|
+
switch (priority) {
|
34
|
+
case 'HIGH':
|
35
|
+
return <SignalHigh className="h-4 w-4" />;
|
36
|
+
case 'MED':
|
37
|
+
return <SignalMedium className="h-4 w-4" />;
|
38
|
+
case 'LOW':
|
39
|
+
return <SignalLow className="h-4 w-4" />;
|
40
|
+
default:
|
41
|
+
return null;
|
42
|
+
}
|
43
|
+
};
|
44
|
+
|
45
|
+
export function PriorityDropdown({ priority, projectId, eventId }: Props) {
|
46
|
+
const [priorityHigh, setPriorityHigh] = useState<Checked>(
|
47
|
+
priority === 'HIGH'
|
48
|
+
);
|
49
|
+
const [priorityMed, setPriorityMed] = useState<Checked>(priority === 'MED');
|
50
|
+
const [priorityLow, setPriorityLow] = useState<Checked>(priority === 'LOW');
|
51
|
+
|
52
|
+
const { mutateAsync: mutatePriority, isPending: isMutatingPriority } =
|
53
|
+
useMutationEventPriority({ projectId: projectId! });
|
54
|
+
|
55
|
+
const handlePriorityChange = async (priority: EventPriorityType) => {
|
56
|
+
await mutatePriority(
|
57
|
+
{ priority, eventIds: [eventId] },
|
58
|
+
{
|
59
|
+
onSuccess: () => {
|
60
|
+
setPriorityHigh(priority === 'HIGH');
|
61
|
+
setPriorityMed(priority === 'MED');
|
62
|
+
setPriorityLow(priority === 'LOW');
|
63
|
+
toast.success('Priority updated successfully!');
|
64
|
+
},
|
65
|
+
onError: (error) => {
|
66
|
+
console.error('Error updating priority:', error);
|
67
|
+
toast.error('Failed to update priority.');
|
68
|
+
},
|
69
|
+
}
|
70
|
+
);
|
71
|
+
};
|
72
|
+
|
73
|
+
return (
|
74
|
+
<DropdownMenu>
|
75
|
+
<DropdownMenuTrigger asChild>
|
76
|
+
<Badge
|
77
|
+
variant="secondary"
|
78
|
+
className="flex items-center gap-1 cursor-pointer"
|
79
|
+
>
|
80
|
+
<PriorityIcon priority={priority} />
|
81
|
+
{priority === 'HIGH' && 'High'}
|
82
|
+
{priority === 'MED' && 'Med'}
|
83
|
+
{priority === 'LOW' && 'Low'}
|
84
|
+
<ChevronDown />
|
85
|
+
</Badge>
|
86
|
+
</DropdownMenuTrigger>
|
87
|
+
<DropdownMenuContent className="w-56">
|
88
|
+
<DropdownMenuLabel>Set Priority</DropdownMenuLabel>
|
89
|
+
<DropdownMenuSeparator />
|
90
|
+
<DropdownMenuCheckboxItem
|
91
|
+
checked={priorityHigh}
|
92
|
+
onCheckedChange={() => handlePriorityChange('HIGH')}
|
93
|
+
className="flex items-center gap-2"
|
94
|
+
>
|
95
|
+
<Badge variant={'secondary'}>
|
96
|
+
<SignalHigh className="h-4 w-4" />
|
97
|
+
High
|
98
|
+
</Badge>
|
99
|
+
</DropdownMenuCheckboxItem>
|
100
|
+
<DropdownMenuCheckboxItem
|
101
|
+
checked={priorityMed}
|
102
|
+
onCheckedChange={() => handlePriorityChange('MED')}
|
103
|
+
className="flex items-center gap-2"
|
104
|
+
>
|
105
|
+
<Badge variant={'secondary'}>
|
106
|
+
<SignalMedium className="h-4 w-4" />
|
107
|
+
Med
|
108
|
+
</Badge>
|
109
|
+
</DropdownMenuCheckboxItem>
|
110
|
+
<DropdownMenuCheckboxItem
|
111
|
+
checked={priorityLow}
|
112
|
+
onCheckedChange={() => handlePriorityChange('LOW')}
|
113
|
+
className="flex items-center gap-2"
|
114
|
+
>
|
115
|
+
<Badge variant={'secondary'}>
|
116
|
+
<SignalLow className="h-4 w-4" />
|
117
|
+
Low
|
118
|
+
</Badge>
|
119
|
+
</DropdownMenuCheckboxItem>
|
120
|
+
</DropdownMenuContent>
|
121
|
+
</DropdownMenu>
|
122
|
+
);
|
123
|
+
}
|
@@ -0,0 +1,225 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import * as React from 'react';
|
4
|
+
import {
|
5
|
+
Activity,
|
6
|
+
AlertCircle,
|
7
|
+
BarChart3,
|
8
|
+
Bell,
|
9
|
+
Bug,
|
10
|
+
Clock,
|
11
|
+
Code,
|
12
|
+
Command,
|
13
|
+
Cpu,
|
14
|
+
Gauge,
|
15
|
+
LineChart,
|
16
|
+
List,
|
17
|
+
Settings2,
|
18
|
+
Shield,
|
19
|
+
Users2,
|
20
|
+
Zap,
|
21
|
+
} from 'lucide-react';
|
22
|
+
|
23
|
+
import { TeamSwitcher } from '@workspace/ui/components/team-switcher';
|
24
|
+
import {
|
25
|
+
Sidebar,
|
26
|
+
SidebarContent,
|
27
|
+
SidebarFooter,
|
28
|
+
SidebarHeader,
|
29
|
+
SidebarRail,
|
30
|
+
} from '@workspace/ui/components/sidebar';
|
31
|
+
import { NavProjects } from './nav-projects';
|
32
|
+
import { NavMain } from './nav-main';
|
33
|
+
import { useQueryProfile } from '@/api/auth/use-query-profile';
|
34
|
+
import { NavUser } from './nav-user';
|
35
|
+
|
36
|
+
const data = {
|
37
|
+
user: {
|
38
|
+
name: 'shadcn',
|
39
|
+
email: 'm@example.com',
|
40
|
+
avatar: '/avatars/shadcn.jpg',
|
41
|
+
},
|
42
|
+
teams: [
|
43
|
+
{
|
44
|
+
name: 'Frontend Team',
|
45
|
+
logo: Code,
|
46
|
+
plan: 'Enterprise',
|
47
|
+
},
|
48
|
+
{
|
49
|
+
name: 'Backend Team',
|
50
|
+
logo: Command,
|
51
|
+
plan: 'Enterprise',
|
52
|
+
},
|
53
|
+
{
|
54
|
+
name: 'DevOps Team',
|
55
|
+
logo: Cpu,
|
56
|
+
plan: 'Free',
|
57
|
+
},
|
58
|
+
],
|
59
|
+
projects: [
|
60
|
+
{
|
61
|
+
name: 'Projects',
|
62
|
+
url: '/project',
|
63
|
+
icon: Activity,
|
64
|
+
},
|
65
|
+
{
|
66
|
+
name: 'Issues',
|
67
|
+
url: '#',
|
68
|
+
icon: Shield,
|
69
|
+
},
|
70
|
+
],
|
71
|
+
navMain: [
|
72
|
+
{
|
73
|
+
title: '대시보드',
|
74
|
+
url: '#',
|
75
|
+
icon: Gauge,
|
76
|
+
isActive: true,
|
77
|
+
items: [
|
78
|
+
{
|
79
|
+
title: '실시간 모니터링',
|
80
|
+
url: '#',
|
81
|
+
},
|
82
|
+
{
|
83
|
+
title: '서비스 상태',
|
84
|
+
url: '#',
|
85
|
+
},
|
86
|
+
{
|
87
|
+
title: '성능 지표',
|
88
|
+
url: '#',
|
89
|
+
},
|
90
|
+
],
|
91
|
+
},
|
92
|
+
{
|
93
|
+
title: '이슈 트래킹',
|
94
|
+
url: '#',
|
95
|
+
icon: Bug,
|
96
|
+
items: [
|
97
|
+
{
|
98
|
+
title: '모든 이슈',
|
99
|
+
url: '#',
|
100
|
+
},
|
101
|
+
{
|
102
|
+
title: '해결되지 않은 이슈',
|
103
|
+
url: '#',
|
104
|
+
},
|
105
|
+
{
|
106
|
+
title: '중요 이슈',
|
107
|
+
url: '#',
|
108
|
+
},
|
109
|
+
{
|
110
|
+
title: '나에게 할당된 이슈',
|
111
|
+
url: '#',
|
112
|
+
},
|
113
|
+
],
|
114
|
+
},
|
115
|
+
{
|
116
|
+
title: '성능',
|
117
|
+
url: '#',
|
118
|
+
icon: Zap,
|
119
|
+
items: [
|
120
|
+
{
|
121
|
+
title: '트랜잭션',
|
122
|
+
url: '#',
|
123
|
+
},
|
124
|
+
{
|
125
|
+
title: '웹 성능',
|
126
|
+
url: '#',
|
127
|
+
},
|
128
|
+
{
|
129
|
+
title: 'API 성능',
|
130
|
+
url: '#',
|
131
|
+
},
|
132
|
+
{
|
133
|
+
title: '백엔드 성능',
|
134
|
+
url: '#',
|
135
|
+
},
|
136
|
+
],
|
137
|
+
},
|
138
|
+
{
|
139
|
+
title: '분석',
|
140
|
+
url: '#',
|
141
|
+
icon: LineChart,
|
142
|
+
items: [
|
143
|
+
{
|
144
|
+
title: '트래픽 분석',
|
145
|
+
url: '#',
|
146
|
+
},
|
147
|
+
{
|
148
|
+
title: '에러 발생률',
|
149
|
+
url: '#',
|
150
|
+
},
|
151
|
+
{
|
152
|
+
title: '사용자 세션',
|
153
|
+
url: '#',
|
154
|
+
},
|
155
|
+
{
|
156
|
+
title: '커스텀 리포트',
|
157
|
+
url: '#',
|
158
|
+
},
|
159
|
+
],
|
160
|
+
},
|
161
|
+
{
|
162
|
+
title: '알림',
|
163
|
+
url: '#',
|
164
|
+
icon: Bell,
|
165
|
+
items: [
|
166
|
+
{
|
167
|
+
title: '알림 설정',
|
168
|
+
url: '#',
|
169
|
+
},
|
170
|
+
{
|
171
|
+
title: '알림 이력',
|
172
|
+
url: '#',
|
173
|
+
},
|
174
|
+
{
|
175
|
+
title: '알림 룰',
|
176
|
+
url: '#',
|
177
|
+
},
|
178
|
+
],
|
179
|
+
},
|
180
|
+
{
|
181
|
+
title: '설정',
|
182
|
+
url: '#',
|
183
|
+
icon: Settings2,
|
184
|
+
items: [
|
185
|
+
{
|
186
|
+
title: '프로젝트 설정',
|
187
|
+
url: '#',
|
188
|
+
},
|
189
|
+
{
|
190
|
+
title: '팀 관리',
|
191
|
+
url: '#',
|
192
|
+
},
|
193
|
+
{
|
194
|
+
title: 'SDK 설정',
|
195
|
+
url: '#',
|
196
|
+
},
|
197
|
+
{
|
198
|
+
title: '통합 설정',
|
199
|
+
url: '#',
|
200
|
+
},
|
201
|
+
],
|
202
|
+
},
|
203
|
+
],
|
204
|
+
};
|
205
|
+
|
206
|
+
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
207
|
+
const { data: profile, isPending: isPendingProfile } = useQueryProfile();
|
208
|
+
|
209
|
+
return (
|
210
|
+
<Sidebar collapsible="icon" {...props}>
|
211
|
+
<SidebarHeader>
|
212
|
+
<TeamSwitcher teams={data.teams} />
|
213
|
+
</SidebarHeader>
|
214
|
+
<SidebarContent>
|
215
|
+
<NavProjects projects={data.projects} />
|
216
|
+
|
217
|
+
<NavMain items={data.navMain} />
|
218
|
+
</SidebarContent>
|
219
|
+
<SidebarFooter>
|
220
|
+
<NavUser profile={profile} />
|
221
|
+
</SidebarFooter>
|
222
|
+
<SidebarRail />
|
223
|
+
</Sidebar>
|
224
|
+
);
|
225
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { ChevronRight, type LucideIcon } from 'lucide-react';
|
4
|
+
|
5
|
+
import {
|
6
|
+
Collapsible,
|
7
|
+
CollapsibleContent,
|
8
|
+
CollapsibleTrigger,
|
9
|
+
} from '@workspace/ui/components/collapsible';
|
10
|
+
import {
|
11
|
+
SidebarGroup,
|
12
|
+
SidebarGroupLabel,
|
13
|
+
SidebarMenu,
|
14
|
+
SidebarMenuButton,
|
15
|
+
SidebarMenuItem,
|
16
|
+
SidebarMenuSub,
|
17
|
+
SidebarMenuSubButton,
|
18
|
+
SidebarMenuSubItem,
|
19
|
+
} from '@workspace/ui/components/sidebar';
|
20
|
+
|
21
|
+
export function NavMain({
|
22
|
+
items,
|
23
|
+
}: {
|
24
|
+
items: {
|
25
|
+
title: string;
|
26
|
+
url: string;
|
27
|
+
icon?: LucideIcon;
|
28
|
+
isActive?: boolean;
|
29
|
+
items?: {
|
30
|
+
title: string;
|
31
|
+
url: string;
|
32
|
+
}[];
|
33
|
+
}[];
|
34
|
+
}) {
|
35
|
+
return (
|
36
|
+
<SidebarGroup>
|
37
|
+
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
38
|
+
<SidebarMenu>
|
39
|
+
{items.map((item) => (
|
40
|
+
<Collapsible
|
41
|
+
key={item.title}
|
42
|
+
asChild
|
43
|
+
defaultOpen={item.isActive}
|
44
|
+
className="group/collapsible"
|
45
|
+
>
|
46
|
+
<SidebarMenuItem>
|
47
|
+
<CollapsibleTrigger asChild>
|
48
|
+
<SidebarMenuButton tooltip={item.title}>
|
49
|
+
{item.icon && <item.icon />}
|
50
|
+
<span>{item.title}</span>
|
51
|
+
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
52
|
+
</SidebarMenuButton>
|
53
|
+
</CollapsibleTrigger>
|
54
|
+
<CollapsibleContent>
|
55
|
+
<SidebarMenuSub>
|
56
|
+
{item.items?.map((subItem) => (
|
57
|
+
<SidebarMenuSubItem key={subItem.title}>
|
58
|
+
<SidebarMenuSubButton asChild>
|
59
|
+
<a href={subItem.url}>
|
60
|
+
<span>{subItem.title}</span>
|
61
|
+
</a>
|
62
|
+
</SidebarMenuSubButton>
|
63
|
+
</SidebarMenuSubItem>
|
64
|
+
))}
|
65
|
+
</SidebarMenuSub>
|
66
|
+
</CollapsibleContent>
|
67
|
+
</SidebarMenuItem>
|
68
|
+
</Collapsible>
|
69
|
+
))}
|
70
|
+
</SidebarMenu>
|
71
|
+
</SidebarGroup>
|
72
|
+
);
|
73
|
+
}
|