chore: add pre-commit hooks, tighten ESLint, activate Sentry DSN, publish CI coverage (Phase 1)
- Install husky v9 + lint-staged: pre-commit runs eslint --fix and prettier on staged files - Tighten ESLint base config: no-console→error, ban-ts-comment (ts-ignore banned, ts-expect-error with description allowed), reportUnusedDisableDirectives→error - Migrate web app from deprecated `next lint` to `eslint src/` with flat config and react-hooks plugin - Convert all 5 @ts-ignore to @ts-expect-error with descriptions, remove stale disable comments - Add NEXT_PUBLIC_SENTRY_DSN to docker-compose.prod.yml and .env.example - Add coverage artifact upload step to CI test job - Pre-existing violations (102 warnings) downgraded to warn in web config for Phase 2 cleanup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -166,7 +166,7 @@ export function ResourcesClient() {
|
||||
const departedFilter = resourceUrlFilters.departed as BooleanFilter;
|
||||
// chapters stored as comma-separated string; empty string means "all chapters visible"
|
||||
const chaptersParam = resourceUrlFilters.chapters;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
const chapterFilter: string[] = useMemo(
|
||||
() => (chaptersParam ? chaptersParam.split(",").filter(Boolean) : []),
|
||||
[chaptersParam],
|
||||
@@ -175,21 +175,32 @@ export function ResourcesClient() {
|
||||
// Flush debounced search input to URL
|
||||
useEffect(() => {
|
||||
setResourceUrlFilters({ search: debouncedSearch });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debouncedSearch]);
|
||||
|
||||
// Keep local search input in sync when URL changes externally
|
||||
useEffect(() => {
|
||||
setSearchInput(resourceUrlFilters.search);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [resourceUrlFilters.search]);
|
||||
|
||||
const setIsActiveFilter = useCallback((v: ActiveFilter) => setResourceUrlFilters({ activeFilter: v }), [setResourceUrlFilters]);
|
||||
const setRolledOffFilter = useCallback((v: BooleanFilter) => setResourceUrlFilters({ rolledOff: v }), [setResourceUrlFilters]);
|
||||
const setDepartedFilter = useCallback((v: BooleanFilter) => setResourceUrlFilters({ departed: v }), [setResourceUrlFilters]);
|
||||
const setChapterFilter = useCallback((v: string[]) => {
|
||||
setResourceUrlFilters({ chapters: v.join(",") });
|
||||
}, [setResourceUrlFilters]);
|
||||
const setIsActiveFilter = useCallback(
|
||||
(v: ActiveFilter) => setResourceUrlFilters({ activeFilter: v }),
|
||||
[setResourceUrlFilters],
|
||||
);
|
||||
const setRolledOffFilter = useCallback(
|
||||
(v: BooleanFilter) => setResourceUrlFilters({ rolledOff: v }),
|
||||
[setResourceUrlFilters],
|
||||
);
|
||||
const setDepartedFilter = useCallback(
|
||||
(v: BooleanFilter) => setResourceUrlFilters({ departed: v }),
|
||||
[setResourceUrlFilters],
|
||||
);
|
||||
const setChapterFilter = useCallback(
|
||||
(v: string[]) => {
|
||||
setResourceUrlFilters({ chapters: v.join(",") });
|
||||
},
|
||||
[setResourceUrlFilters],
|
||||
);
|
||||
|
||||
const [includeProposedChargeability, setIncludeProposedChargeability] = useState(false);
|
||||
const [hiddenCountryIds, setHiddenCountryIds] = useState<string[]>([]);
|
||||
@@ -412,7 +423,13 @@ export function ResourcesClient() {
|
||||
|
||||
function clearAll() {
|
||||
setSearchInput("");
|
||||
setResourceUrlFilters({ search: "", activeFilter: "active", rolledOff: DEFAULT_BOOLEAN_FILTER, departed: DEFAULT_BOOLEAN_FILTER, chapters: "" });
|
||||
setResourceUrlFilters({
|
||||
search: "",
|
||||
activeFilter: "active",
|
||||
rolledOff: DEFAULT_BOOLEAN_FILTER,
|
||||
departed: DEFAULT_BOOLEAN_FILTER,
|
||||
chapters: "",
|
||||
});
|
||||
setHiddenCountryIds([]);
|
||||
setIncludeWithoutCountry(true);
|
||||
setHiddenResourceTypes([...DEFAULT_HIDDEN_RESOURCE_TYPES]);
|
||||
@@ -468,7 +485,9 @@ export function ResourcesClient() {
|
||||
if (next.length === chapters.length) {
|
||||
setChapterFilter([]);
|
||||
} else {
|
||||
setChapterFilter(next.sort((left, right) => chapters.indexOf(left) - chapters.indexOf(right)));
|
||||
setChapterFilter(
|
||||
next.sort((left, right) => chapters.indexOf(left) - chapters.indexOf(right)),
|
||||
);
|
||||
}
|
||||
},
|
||||
[chapters, chapterFilter, setChapterFilter],
|
||||
@@ -533,13 +552,23 @@ export function ResourcesClient() {
|
||||
{ header: "LCR (cents)", accessor: (r) => r.lcrCents },
|
||||
{ header: "Currency", accessor: (r) => r.currency },
|
||||
{ header: "Chargeability Target", accessor: (r) => r.chargeabilityTarget },
|
||||
{ header: "Active", accessor: (r) => r.isActive ? "Yes" : "No" },
|
||||
{ header: "Active", accessor: (r) => (r.isActive ? "Yes" : "No") },
|
||||
]);
|
||||
downloadCsv(csv, `resources-export-${new Date().toISOString().slice(0, 10)}.csv`);
|
||||
}, [displayedResources, selection.selectedIds]);
|
||||
|
||||
const chips = [
|
||||
...(search ? [{ label: `Search: "${search}"`, onRemove: () => { setSearchInput(""); setResourceUrlFilters({ search: "" }); } }] : []),
|
||||
...(search
|
||||
? [
|
||||
{
|
||||
label: `Search: "${search}"`,
|
||||
onRemove: () => {
|
||||
setSearchInput("");
|
||||
setResourceUrlFilters({ search: "" });
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(chapterFilter.length > 0
|
||||
? [
|
||||
{
|
||||
@@ -1303,7 +1332,12 @@ export function ResourcesClient() {
|
||||
/>
|
||||
</div>
|
||||
{isOverflow && (
|
||||
<span className="text-[9px] font-bold text-green-600 dark:text-green-400" title={`${actual}% actual`}>+</span>
|
||||
<span
|
||||
className="text-[9px] font-bold text-green-600 dark:text-green-400"
|
||||
title={`${actual}% actual`}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user