Skip to content

Commit 97b86d7

Browse files
authored
Merge pull request #189 from koya0/development
feat: add availability filter for the issues
2 parents d7f21f5 + 2274282 commit 97b86d7

File tree

5 files changed

+249
-144
lines changed

5 files changed

+249
-144
lines changed

src/home/fetch-github/fetch-and-display-previews.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { applyAvatarsToIssues, renderGitHubIssues } from "../rendering/render-gi
44
import { renderOrgHeaderLabel } from "../rendering/render-org-header";
55
import { closeModal } from "../rendering/render-preview-modal";
66
import { filterIssuesBySearch } from "../sorting/filter-issues-by-search";
7+
import { filterIssuesByAvailability } from "../sorting/filter-issues-by-availability";
78
import { Sorting } from "../sorting/generate-sorting-buttons";
89
import { sortIssuesController } from "../sorting/sort-issues-controller";
910
import { checkCacheIntegrityAndSyncTasks } from "./cache-integrity";
@@ -12,6 +13,12 @@ export type Options = {
1213
ordering: "normal" | "reverse";
1314
};
1415

16+
export let isFilteringAvailableIssues = true;
17+
18+
export function swapAvailabilityFilter() {
19+
isFilteringAvailableIssues = !isFilteringAvailableIssues;
20+
}
21+
1522
// start at view based on URL
1623
export let isProposalOnlyViewer = new URLSearchParams(window.location.search).get("proposal") === "true";
1724

@@ -87,20 +94,16 @@ export async function displayGitHubIssues({
8794
const sortedIssues = sortIssuesController(cachedTasks, sorting, options);
8895
let sortedAndFiltered = sortedIssues.filter(getProposalsOnlyFilter(isProposalOnlyViewer));
8996
sortedAndFiltered = filterIssuesByOrganization(sortedAndFiltered);
97+
sortedAndFiltered = isFilteringAvailableIssues ? filterIssuesByAvailability(sortedAndFiltered) : sortedAndFiltered;
9098
renderGitHubIssues(sortedAndFiltered, skipAnimation);
9199
applyAvatarsToIssues();
92100
}
93101

94-
export async function searchDisplayGitHubIssues({
95-
searchText,
96-
skipAnimation = false,
97-
}: {
98-
searchText: string;
99-
skipAnimation?: boolean;
100-
}) {
102+
export async function searchDisplayGitHubIssues({ searchText, skipAnimation = false }: { searchText: string; skipAnimation?: boolean }) {
101103
const searchResult = filterIssuesBySearch(searchText);
102104
let filteredIssues = searchResult.filter(getProposalsOnlyFilter(isProposalOnlyViewer));
103105
filteredIssues = filterIssuesByOrganization(filteredIssues);
106+
filteredIssues = isFilteringAvailableIssues ? filterIssuesByAvailability(filteredIssues) : filteredIssues;
104107
renderGitHubIssues(filteredIssues, skipAnimation);
105108
applyAvatarsToIssues();
106109
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { GitHubIssue } from "../github-types";
2+
3+
export function filterIssuesByAvailability(issues: GitHubIssue[]) {
4+
return issues.filter((issue) => {
5+
if (issue.assignee) return false;
6+
return true;
7+
});
8+
}

src/home/sorting/sorting-manager.ts

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { displayGitHubIssues, searchDisplayGitHubIssues } from "../fetch-github/
22
import { renderErrorInModal } from "../rendering/display-popup-modal";
33
import { proposalViewToggle } from "../rendering/render-github-issues";
44
import { Sorting } from "./generate-sorting-buttons";
5+
import { isFilteringAvailableIssues, swapAvailabilityFilter } from "../fetch-github/fetch-and-display-previews";
56

67
export class SortingManager {
78
private _lastChecked: HTMLInputElement | null = null;
89
private _toolBarFilters: HTMLElement;
9-
private _filterTextBox: HTMLInputElement;
10+
private _filtersDiv: HTMLElement;
1011
private _sortingButtons: HTMLElement;
1112
private _instanceId: string;
1213
private _sortingState: { [key: string]: "unsorted" | "ascending" | "descending" } = {}; // Track state for each sorting option
@@ -20,8 +21,12 @@ export class SortingManager {
2021

2122
// Initialize sorting buttons first
2223
this._sortingButtons = this._generateSortingButtons(sortingOptions);
23-
// Then initialize filter text box
24-
this._filterTextBox = this._generateFilterTextBox();
24+
// Initialize filters div
25+
this._filtersDiv = this._generateFiltersDiv();
26+
// Add filter search box to filters div
27+
this._filtersDiv.appendChild(this._generateFilterTextBox());
28+
// Add filter available issues button to filters div
29+
this._filtersDiv.appendChild(this._generateFilterAvailableIssuesButton());
2530

2631
// Initialize sorting states to 'unsorted' for all options
2732
sortingOptions.forEach((option) => {
@@ -30,10 +35,16 @@ export class SortingManager {
3035
}
3136

3237
public render() {
33-
this._toolBarFilters.appendChild(this._filterTextBox);
38+
this._toolBarFilters.appendChild(this._filtersDiv);
3439
this._toolBarFilters.appendChild(this._sortingButtons);
3540
}
3641

42+
private _generateFiltersDiv() {
43+
const div = document.createElement("div");
44+
div.className = "filters";
45+
return div;
46+
}
47+
3748
private _generateFilterTextBox() {
3849
const textBox = document.createElement("input");
3950
textBox.type = "text";
@@ -115,10 +126,19 @@ export class SortingManager {
115126
return textBox;
116127
}
117128

129+
private _resetSearchBar() {
130+
const filterTextBox = this._filtersDiv.querySelector('input[type="text"]') as HTMLInputElement;
131+
filterTextBox.value = "";
132+
const newURL = new URL(window.location.href);
133+
newURL.searchParams.delete("search");
134+
window.history.replaceState({}, "", newURL.toString());
135+
}
136+
118137
private _resetSortButtons() {
119138
this._sortingButtons.querySelectorAll('input[type="radio"]').forEach((input) => {
120139
if (input instanceof HTMLInputElement) {
121140
input.checked = false;
141+
this._sortingState[input.value] = "unsorted";
122142
input.setAttribute("data-ordering", "");
123143
}
124144
});
@@ -148,6 +168,54 @@ export class SortingManager {
148168
return buttons;
149169
}
150170

171+
private _generateFilterAvailableIssuesButton() {
172+
const input = document.createElement("input");
173+
input.type = "button";
174+
input.value = "Unassigned";
175+
input.id = `filter-availability-${this._instanceId}`;
176+
177+
input.addEventListener("click", () => {
178+
swapAvailabilityFilter();
179+
input.value = isFilteringAvailableIssues ? "Unassigned" : "All Issues";
180+
181+
try {
182+
// Clear search when applying the filter
183+
this._resetSearchBar();
184+
185+
const { sortingOption, sortingOrder } = this._detectSortingState();
186+
void displayGitHubIssues({
187+
sorting: sortingOption as Sorting,
188+
options: { ordering: sortingOrder },
189+
});
190+
} catch (error) {
191+
renderErrorInModal(error as Error);
192+
}
193+
});
194+
195+
return input;
196+
}
197+
198+
private _detectSortingState() {
199+
let sortingOption;
200+
let sortingOrder = "normal";
201+
202+
for (const option of Object.keys(this._sortingState)) {
203+
const order = this._sortingState[option];
204+
205+
if (order !== "unsorted") {
206+
sortingOption = option;
207+
208+
if (order === "descending") {
209+
sortingOrder = "normal";
210+
} else if (order === "ascending") {
211+
sortingOrder = "reverse";
212+
}
213+
break;
214+
}
215+
}
216+
return { sortingOption, sortingOrder };
217+
}
218+
151219
private _createRadioButton(option: string): HTMLInputElement {
152220
const input = document.createElement("input");
153221
input.type = "radio";
@@ -168,35 +236,26 @@ export class SortingManager {
168236
const currentOrdering = input.getAttribute("data-ordering");
169237
let newOrdering: string;
170238

239+
// Reset sort buttons
240+
this._resetSortButtons();
241+
171242
// Determine the new ordering based on the current state
172243
if (currentOrdering === "normal") {
173244
newOrdering = "reverse";
245+
this._sortingState[option] = "ascending";
174246
} else if (currentOrdering === "reverse") {
175247
newOrdering = "disabled";
248+
this._sortingState[option] = "unsorted";
176249
} else {
177250
newOrdering = "normal";
251+
this._sortingState[option] = "descending";
178252
}
179253

180254
// Apply the new ordering state
181255
input.setAttribute("data-ordering", newOrdering);
182-
input.parentElement?.childNodes.forEach((node) => {
183-
if (node instanceof HTMLInputElement) {
184-
node.setAttribute("data-ordering", "");
185-
}
186-
});
187256

188257
// Clear search when applying a different sort
189-
this._filterTextBox.value = "";
190-
const newURL = new URL(window.location.href);
191-
newURL.searchParams.delete("search");
192-
window.history.replaceState({}, "", newURL.toString());
193-
194-
// Reset other buttons
195-
input.parentElement?.childNodes.forEach((node) => {
196-
if (node instanceof HTMLInputElement) {
197-
node.setAttribute("data-ordering", "");
198-
}
199-
});
258+
this._resetSearchBar();
200259

201260
if (newOrdering === "disabled") {
202261
this._lastChecked = null;
@@ -209,7 +268,10 @@ export class SortingManager {
209268

210269
// Apply the sorting based on the new state (normal or reverse)
211270
try {
212-
void displayGitHubIssues({ sorting: option as Sorting, options: { ordering: newOrdering } });
271+
void displayGitHubIssues({
272+
sorting: option as Sorting,
273+
options: { ordering: newOrdering },
274+
});
213275
} catch (error) {
214276
renderErrorCatch(error as ErrorEvent);
215277
}

0 commit comments

Comments
 (0)