@@ -458,12 +471,12 @@ function ChildPoliciesTable(props: {
// will need to account for the whole parent tree
editingDisabled
? policy?.parent?.value.id
- ? updatedPolicyScores[policy?.parent?.value.id]
+ ? (updatedPolicyScores[policy?.parent?.value.id]
?.userStrikeCount ??
- policy?.parent?.value?.userStrikeCount
+ policy?.parent?.value?.userStrikeCount)
: undefined
- : updatedPolicyScores[policy.value.id]?.userStrikeCount ??
- policy.value.userStrikeCount
+ : (updatedPolicyScores[policy.value.id]?.userStrikeCount ??
+ policy.value.userStrikeCount)
}
placeholder="1"
onChange={(value) => {
diff --git a/client/src/webpages/dashboard/userStrikes/StrikeAnalyticsTab.tsx b/client/src/webpages/dashboard/userStrikes/StrikeAnalyticsTab.tsx
index ee1ff84..ab36d02 100644
--- a/client/src/webpages/dashboard/userStrikes/StrikeAnalyticsTab.tsx
+++ b/client/src/webpages/dashboard/userStrikes/StrikeAnalyticsTab.tsx
@@ -68,20 +68,24 @@ function UserStrikeDistributionChart() {
if (loading || thresholdsLoading) {
return ;
}
+ const chartHeight = 400;
+
return (
Distribution of User Strikes
-
-
+ {/* Pin the tooltip to a fixed Y in the middle of the plot area so
+ it doesn't follow the cursor up and down inside a single bar.
+ The +20 nudges it below the title without overlapping the x-axis
+ label. The cursor fill replaces the invisible default so the
+ user can see which column is selected. */}
+
+ {/* Top alignment keeps the legend out of the x-axis label's space.
+ Custom payload so the dashed red threshold lines are explained;
+ ReferenceLine doesn't participate in the legend on its own. */}
+
);
}
diff --git a/client/src/webpages/dashboard/userStrikes/ThresholdsAndSettingsTab.tsx b/client/src/webpages/dashboard/userStrikes/ThresholdsAndSettingsTab.tsx
index 368556a..459e236 100644
--- a/client/src/webpages/dashboard/userStrikes/ThresholdsAndSettingsTab.tsx
+++ b/client/src/webpages/dashboard/userStrikes/ThresholdsAndSettingsTab.tsx
@@ -66,7 +66,11 @@ export default function ThresholdsTab() {
},
});
- const [setUserStrikeTTL] = useGQLUpdateUserStrikeTtlMutation();
+ const [setUserStrikeTTL] = useGQLUpdateUserStrikeTtlMutation({
+ onCompleted: async () => {
+ await refetchThresholds();
+ },
+ });
const thresholds = data?.myOrg?.userStrikeThresholds;
@@ -111,6 +115,9 @@ export default function ThresholdsTab() {
}}
/>
{
await setUserStrikeTTL({
@@ -127,7 +134,9 @@ export default function ThresholdsTab() {
}
function StrikeTTLForm(props: {
orgTTL: number;
- setTTL: (ttl: number) => void;
+ // Async — the caller awaits inside the Save handler so the form can wait for
+ // the mutation to round-trip before toggling out of edit mode.
+ setTTL: (ttl: number) => Promise;
}) {
const { orgTTL, setTTL } = props;
@@ -180,7 +189,7 @@ function StrikeTTLForm(props: {
className="!fill-none"
startIcon={Check}
onClick={async () => {
- setTTL(ttlFormState);
+ await setTTL(ttlFormState);
toggleEditing();
}}
>
@@ -196,17 +205,21 @@ function StrikeTTLForm(props: {
User strikes stay on record for
-
+
{
if (value.target.value === '') {
setTTLFormState(0);
@@ -219,6 +232,7 @@ function StrikeTTLForm(props: {
}
}}
/>
+ days
diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs
index 825056e..df5d241 100644
--- a/lint-staged.config.mjs
+++ b/lint-staged.config.mjs
@@ -18,7 +18,11 @@ const eslintInPackage = (pkg) => (files) => {
.filter((f) => f.startsWith(pkgRoot + path.sep))
.map((f) => path.relative(pkgRoot, f));
if (rels.length === 0) return [];
- const args = rels.map((f) => JSON.stringify(f)).join(' ');
+ // Single-quote each path so it survives the outer double-quoted `bash -c`.
+ // The previous JSON.stringify produced double-quoted args, which collapsed
+ // against the outer `"..."` and left eslint with no file args — silently
+ // making it lint the whole package directory instead.
+ const args = rels.map((f) => `'${f.replace(/'/g, `'\\''`)}'`).join(' ');
return `bash -c "cd ${pkg} && ./node_modules/.bin/eslint --fix ${args}"`;
};