Skip to content

Commit fdb852c

Browse files
committed
added_new_feature
1 parent fc336dc commit fdb852c

File tree

4 files changed

+176
-115
lines changed

4 files changed

+176
-115
lines changed

src/components/PRTable.tsx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ function StatusBadge({ pr }: { pr: PullRequest }) {
3030
);
3131
}
3232

33-
function CopyButton({ text }: { text: string }) {
33+
function CopyButton({ text, isChecked, onToggleCheck }: {
34+
text: string;
35+
isChecked: boolean;
36+
onToggleCheck: () => void;
37+
}) {
3438
const [copied, setCopied] = useState(false);
3539

3640
const handleCopy = async () => {
@@ -40,17 +44,26 @@ function CopyButton({ text }: { text: string }) {
4044
};
4145

4246
return (
43-
<button
44-
onClick={handleCopy}
45-
className="p-1 text-gray-500 hover:text-gray-700 transition-colors"
46-
title="Copy link"
47-
>
48-
{copied ? (
49-
<Check size={14} className="text-green-500" />
50-
) : (
51-
<Copy size={14} />
52-
)}
53-
</button>
47+
<div className="flex items-center gap-2">
48+
<button
49+
onClick={handleCopy}
50+
className="p-1 text-gray-500 hover:text-gray-700 transition-colors"
51+
title="Copy link"
52+
>
53+
{copied ? (
54+
<Check size={14} className="text-green-500" />
55+
) : (
56+
<Copy size={14} />
57+
)}
58+
</button>
59+
<input
60+
type="checkbox"
61+
checked={isChecked}
62+
onChange={onToggleCheck}
63+
className="w-4 h-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500"
64+
title="Mark as points allocated"
65+
/>
66+
</div>
5467
);
5568
}
5669

@@ -72,7 +85,7 @@ function LabelBadge({ name, color }: { name: string; color: string }) {
7285
}
7386

7487
export function PRTable() {
75-
const { pullRequests, isLoading } = useGithubStore();
88+
const { pullRequests, isLoading, checkedPRs, togglePRCheck } = useGithubStore();
7689
const [sortField, setSortField] = useState<keyof PullRequest>('number');
7790
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
7891
const [filters, setFilters] = useState({
@@ -272,7 +285,11 @@ export function PRTable() {
272285
#{pr.number}
273286
<ExternalLink size={14} />
274287
</a>
275-
<CopyButton text={pr.html_url} />
288+
<CopyButton
289+
text={pr.html_url}
290+
isChecked={checkedPRs[pr.html_url] || false}
291+
onToggleCheck={() => togglePRCheck(pr.html_url)}
292+
/>
276293
</div>
277294
</td>
278295
<td className="px-6 py-4 text-sm text-gray-500">

src/components/RepoInput.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,33 @@ export function RepoInput() {
4343
<div className="mt-4">
4444
<h3 className="text-sm font-medium text-gray-700 mb-2">Favorite Repositories</h3>
4545
<div className="flex flex-wrap gap-2">
46-
{favorites.map((url) => (
47-
<button
48-
key={url}
49-
onClick={() => {
50-
setRepoUrl(url);
51-
setRepository(url);
52-
}}
53-
className="inline-flex items-center px-3 py-1 rounded-full text-xs sm:text-sm bg-gray-100 text-gray-700 hover:bg-gray-200"
54-
>
55-
<Star size={14} className="mr-1 text-yellow-500" />
56-
<span className="truncate max-w-[150px] sm:max-w-[200px]">
57-
{url.split('/').slice(-2).join('/')}
58-
</span>
59-
</button>
60-
))}
46+
{favorites.map((url) => {
47+
const checkedCount = Object.entries(useGithubStore.getState().checkedPRs)
48+
.filter(([prUrl, checked]) =>
49+
prUrl.includes(url.split('/').slice(-2).join('/')) && checked
50+
).length;
51+
52+
return (
53+
<button
54+
key={url}
55+
onClick={() => {
56+
setRepoUrl(url);
57+
setRepository(url);
58+
}}
59+
className="inline-flex items-center px-3 py-1 rounded-full text-xs sm:text-sm bg-gray-100 text-gray-700 hover:bg-gray-200"
60+
>
61+
<Star size={14} className="mr-1 text-yellow-500" />
62+
<span className="truncate max-w-[150px] sm:max-w-[200px]">
63+
{url.split('/').slice(-2).join('/')}
64+
</span>
65+
{checkedCount > 0 && (
66+
<span className="ml-2 px-1.5 py-0.5 bg-green-100 text-green-800 rounded-full text-xs">
67+
{checkedCount}
68+
</span>
69+
)}
70+
</button>
71+
);
72+
})}
6173
</div>
6274
</div>
6375
)}

src/store/githubStore.ts

Lines changed: 112 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { create } from 'zustand';
22
import { Octokit } from '@octokit/rest';
33
import type { PullRequest, Repository } from '../types/github';
4+
import { persist } from 'zustand/middleware';
45

56
interface GithubStore {
67
repository: Repository | null;
78
pullRequests: PullRequest[];
89
isLoading: boolean;
910
error: string | null;
11+
checkedPRs: Record<string, boolean>;
1012
setRepository: (repoUrl: string) => Promise<void>;
1113
fetchPullRequests: () => Promise<void>;
14+
togglePRCheck: (prUrl: string) => void;
1215
}
1316

1417
const parseRepoUrl = (url: string): Repository | null => {
@@ -34,100 +37,123 @@ const octokit = new Octokit({
3437
},
3538
});
3639

37-
export const useGithubStore = create<GithubStore>((set, get) => ({
38-
repository: null,
39-
pullRequests: [],
40-
isLoading: false,
41-
error: null,
40+
export const useGithubStore = create<GithubStore>()(
41+
persist(
42+
(set, get) => ({
43+
repository: null,
44+
pullRequests: [],
45+
isLoading: false,
46+
error: null,
47+
checkedPRs: {},
4248

43-
setRepository: async (repoUrl: string) => {
44-
const repo = parseRepoUrl(repoUrl);
45-
if (!repo) {
46-
set({ error: 'Invalid repository URL' });
47-
return;
48-
}
49+
setRepository: async (repoUrl: string) => {
50+
const repo = parseRepoUrl(repoUrl);
51+
if (!repo) {
52+
set({ error: 'Invalid repository URL' });
53+
return;
54+
}
4955

50-
set({ repository: repo, error: null });
51-
await get().fetchPullRequests();
52-
},
56+
set((state) => ({
57+
repository: repo,
58+
error: null,
59+
checkedPRs: state.checkedPRs
60+
}));
61+
await get().fetchPullRequests();
62+
},
5363

54-
fetchPullRequests: async () => {
55-
const { repository } = get();
56-
if (!repository) return;
64+
fetchPullRequests: async () => {
65+
const { repository } = get();
66+
if (!repository) return;
5767

58-
set({ isLoading: true, error: null });
68+
set({ isLoading: true, error: null });
5969

60-
try {
61-
// Check rate limit first
62-
const { data: rateLimit } = await octokit.rateLimit.get();
63-
if (rateLimit.rate.remaining === 0) {
64-
const resetDate = new Date(rateLimit.rate.reset * 1000);
65-
throw new Error(`API rate limit exceeded. Resets at ${resetDate.toLocaleTimeString()}`);
66-
}
70+
try {
71+
// Check rate limit first
72+
const { data: rateLimit } = await octokit.rateLimit.get();
73+
if (rateLimit.rate.remaining === 0) {
74+
const resetDate = new Date(rateLimit.rate.reset * 1000);
75+
throw new Error(`API rate limit exceeded. Resets at ${resetDate.toLocaleTimeString()}`);
76+
}
6777

68-
const { data: prs } = await octokit.pulls.list({
69-
owner: repository.owner,
70-
repo: repository.name,
71-
state: 'all',
72-
sort: 'updated',
73-
direction: 'desc',
74-
per_page: 100,
75-
});
78+
const { data: prs } = await octokit.pulls.list({
79+
owner: repository.owner,
80+
repo: repository.name,
81+
state: 'all',
82+
sort: 'updated',
83+
direction: 'desc',
84+
per_page: 100,
85+
});
7686

77-
// Fetch merged status and comments for each PR
78-
const prsWithDetails = await Promise.all(
79-
prs.map(async (pr) => {
80-
try {
81-
const [prDetails, reviewComments, issueComments] = await Promise.all([
82-
pr.state === 'closed' ?
83-
octokit.pulls.get({
84-
owner: repository.owner,
85-
repo: repository.name,
86-
pull_number: pr.number,
87-
}) :
88-
Promise.resolve({ data: { merged: false } }),
89-
octokit.pulls.listReviewComments({
90-
owner: repository.owner,
91-
repo: repository.name,
92-
pull_number: pr.number,
93-
per_page: 100,
94-
}),
95-
octokit.issues.listComments({
96-
owner: repository.owner,
97-
repo: repository.name,
98-
issue_number: pr.number,
99-
per_page: 100,
100-
})
101-
]);
87+
// Fetch merged status and comments for each PR
88+
const prsWithDetails = await Promise.all(
89+
prs.map(async (pr) => {
90+
try {
91+
const [prDetails, reviewComments, issueComments] = await Promise.all([
92+
pr.state === 'closed' ?
93+
octokit.pulls.get({
94+
owner: repository.owner,
95+
repo: repository.name,
96+
pull_number: pr.number,
97+
}) :
98+
Promise.resolve({ data: { merged: false } }),
99+
octokit.pulls.listReviewComments({
100+
owner: repository.owner,
101+
repo: repository.name,
102+
pull_number: pr.number,
103+
per_page: 100,
104+
}),
105+
octokit.issues.listComments({
106+
owner: repository.owner,
107+
repo: repository.name,
108+
issue_number: pr.number,
109+
per_page: 100,
110+
})
111+
]);
102112

103-
const totalComments = reviewComments.data.length + issueComments.data.length;
113+
const totalComments = reviewComments.data.length + issueComments.data.length;
104114

105-
return {
106-
...pr,
107-
merged: prDetails.data.merged || false,
108-
comments: totalComments
109-
} as PullRequest;
110-
} catch (err) {
111-
console.error(`Error fetching details for PR #${pr.number}:`, err);
112-
return {
113-
...pr,
114-
merged: false,
115-
comments: 0
116-
} as PullRequest;
117-
}
118-
})
119-
);
115+
return {
116+
...pr,
117+
merged: prDetails.data.merged || false,
118+
comments: totalComments
119+
} as PullRequest;
120+
} catch (err) {
121+
console.error(`Error fetching details for PR #${pr.number}:`, err);
122+
return {
123+
...pr,
124+
merged: false,
125+
comments: 0
126+
} as PullRequest;
127+
}
128+
})
129+
);
120130

121-
set({ pullRequests: prsWithDetails, isLoading: false });
122-
} catch (err) {
123-
console.error('Failed to fetch pull requests:', err);
124-
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch pull requests';
125-
set({
126-
error: errorMessage.includes('rate limit') ?
127-
errorMessage :
128-
'Failed to fetch pull requests. Please check the repository URL and your access permissions.',
129-
isLoading: false
130-
});
131+
set({ pullRequests: prsWithDetails, isLoading: false });
132+
} catch (err) {
133+
console.error('Failed to fetch pull requests:', err);
134+
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch pull requests';
135+
set({
136+
error: errorMessage.includes('rate limit') ?
137+
errorMessage :
138+
'Failed to fetch pull requests. Please check the repository URL and your access permissions.',
139+
isLoading: false
140+
});
141+
}
142+
},
143+
144+
togglePRCheck: (prUrl: string) =>
145+
set((state) => ({
146+
checkedPRs: {
147+
...state.checkedPRs,
148+
[prUrl]: !state.checkedPRs[prUrl]
149+
}
150+
})),
151+
}),
152+
{
153+
name: 'github-store',
154+
partialize: (state) => ({
155+
checkedPRs: state.checkedPRs,
156+
}),
131157
}
132-
},
133-
}));
158+
)
159+
);

src/types/github.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,10 @@ export interface Repository {
2828
owner: string;
2929
name: string;
3030
full_name: string;
31+
checkedPRs?: CheckedPR[];
32+
}
33+
34+
export interface CheckedPR {
35+
url: string;
36+
checked: boolean;
3137
}

0 commit comments

Comments
 (0)