Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@
"assign_a_volunteer_to": "Assign a volunteer to {{name}}",
"assign_bed": "Assign Bed",
"assign_bed_now": "Assign Bed Now",
"assign_color": "Assign Color",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Move newly added i18n keys to the end of en.json.

Both keys were inserted inline; this repository requires new keys in public/locale/en.json to be appended at the end of the file.

🔧 Proposed fix
-  "assign_bed_now": "Assign Bed Now",
-  "assign_color": "Assign Color",
+  "assign_bed_now": "Assign Bed Now",
   "assign_location": "Assign Location",
-  "showing_x_of_y": "Showing <strong>{{x}}</strong> of <strong>{{y}}</strong>",
-  "shuffle_color": "Shuffle Color",
+  "showing_x_of_y": "Showing <strong>{{x}}</strong> of <strong>{{y}}</strong>",
   "sidebar": "sidebar",
-  "zoom_out": "Zoom Out",
-  "℞": "℞"
+  "zoom_out": "Zoom Out",
+  "℞": "℞",
+  "assign_color": "Assign Color",
+  "shuffle_color": "Shuffle Color"
 }

As per coding guidelines: "Append missing i18n keys directly to the end of the public/locale/en.json file without reading i18n files".

Also applies to: 5509-5509

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/locale/en.json` at line 781, The i18n key "assign_color" was inserted
inline; move this new key (and any other newly added keys such as the one around
lines flagged 5509) to the very end of the en.json file instead of inserting
them mid-file, ensuring keys are appended in a single group at the file's end so
the repository's guideline (append new i18n keys to the end) is followed; locate
occurrences of "assign_color" and any other recently added keys and cut/paste
them to the file's end in the same JSON object order.

"assign_location": "Assign Location",
"assign_service_points": "Assign Service points",
"assign_this_encounter": "Assign This Encounter",
Expand Down Expand Up @@ -5505,6 +5506,7 @@
"showing_all_medications": "Showing all medications",
"showing_count_templates": "Showing {{count}} template(s)",
"showing_x_of_y": "Showing <strong>{{x}}</strong> of <strong>{{y}}</strong>",
"shuffle_color": "Shuffle Color",
"sidebar": "sidebar",
"sidebar_description": "sidebar provides navigation to different sections",
"sign_in": "Sign in",
Expand Down
78 changes: 78 additions & 0 deletions src/pages/Admin/TagConfig/TagConfigForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import CareIcon from "@/CAREUI/icons/CareIcon";
import RoleOrgSelector from "@/components/Common/RoleOrgSelector";
import FacilityOrganizationSelector from "@/pages/Facility/settings/organizations/components/FacilityOrganizationSelector";

import { Badge } from "@/components/ui/badge";

import { Button } from "@/components/ui/button";
import {
Form,
Expand All @@ -29,6 +31,19 @@ import {
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";

const TAG_COLORS = [
"#B91C1C", // Red
"#C2410C", // Orange
"#A16207", // Amber
"#15803D", // Green
"#0F766E", // Teal
"#1D4ED8", // Blue
"#7E22CE", // Purple
"#BE185D", // Pink
"#0E7490", // Cyan
"#4D7C0F", // Lime
] as const;
Comment on lines +34 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the contrast ratios using a simple Python script
pip install wcag-contrast-ratio 2>/dev/null

python3 << 'EOF'
# Calculate contrast ratios for TAG_COLORS against their tinted backgrounds
# The background is the color with 40% opacity (25% actual due to hex)
# approximated by blending with white

def hex_to_rgb(hex_color):
    hex_color = hex_color.lstrip('#')
    return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))

def blend_with_white(rgb, alpha):
    """Blend color with white background at given alpha"""
    return tuple(int(c * alpha + 255 * (1 - alpha)) for c in rgb)

def relative_luminance(rgb):
    """Calculate relative luminance per WCAG"""
    def adjust(c):
        c = c / 255
        return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
    r, g, b = [adjust(c) for c in rgb]
    return 0.2126 * r + 0.7152 * g + 0.0722 * b

def contrast_ratio(l1, l2):
    lighter = max(l1, l2)
    darker = min(l1, l2)
    return (lighter + 0.05) / (darker + 0.05)

TAG_COLORS = [
    ("#B91C1C", "Red"),
    ("#C2410C", "Orange"),
    ("#A16207", "Amber"),
    ("#15803D", "Green"),
    ("#0F766E", "Teal"),
    ("#1D4ED8", "Blue"),
    ("#7E22CE", "Purple"),
    ("#BE185D", "Pink"),
    ("#0E7490", "Cyan"),
    ("#4D7C0F", "Lime"),
]

print("Color Contrast Analysis (text color vs 40% opacity background on white):")
print("=" * 70)
for color, name in TAG_COLORS:
    fg_rgb = hex_to_rgb(color)
    # 0x40 = 64 in decimal, 64/255 ≈ 0.25 alpha
    bg_rgb = blend_with_white(fg_rgb, 0.25)
    
    fg_lum = relative_luminance(fg_rgb)
    bg_lum = relative_luminance(bg_rgb)
    ratio = contrast_ratio(fg_lum, bg_lum)
    
    status = "✓ PASS" if ratio >= 4.5 else "✗ FAIL"
    print(f"{name:8} {color}: {ratio:.2f}:1 {status} (AA requires 4.5:1)")

EOF

Repository: ohcnetwork/care_fe

Length of output: 1618


🏁 Script executed:

fd "TagBadge" --type f

Repository: ohcnetwork/care_fe

Length of output: 94


🏁 Script executed:

cat -n src/components/Tags/TagBadge.tsx

Repository: ohcnetwork/care_fe

Length of output: 1548


Most colors fail WCAG AA contrast requirements and must be adjusted.

Verification confirms that 9 out of 10 colors fail to meet WCAG AA 4.5:1 contrast ratio when used as text on their 40% opacity backgrounds:

  • Red (#B91C1C): 4.16:1 ✗
  • Orange (#C2410C): 3.54:1 ✗
  • Amber (#A16207): 3.49:1 ✗
  • Green (#15803D): 3.53:1 ✗
  • Teal (#0F766E): 3.80:1 ✗
  • Blue (#1D4ED8): 4.45:1 ✗
  • Purple (#7E22CE): 4.51:1 ✓
  • Pink (#BE185D): 3.91:1 ✗
  • Cyan (#0E7490): 3.74:1 ✗
  • Lime (#4D7C0F): 3.56:1 ✗

This violates accessibility requirements for clinical environments. Consider using lighter or darker colors to achieve sufficient contrast, or implement an alternative approach such as using white/black text with these colored backgrounds instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Admin/TagConfig/TagConfigForm.tsx` around lines 34 - 45, TAG_COLORS
in TagConfigForm.tsx contains hues that fail WCAG AA 4.5:1 contrast when used as
text on their 40% opacity backgrounds; either replace the hex values in
TAG_COLORS with colors validated to meet 4.5:1 on a 40% background or keep the
existing palette but update the rendering logic (e.g., in the Tag component /
label renderer) to compute and use an accessible foreground color (black or
white) per color using a contrast check function; ensure the chosen approach is
applied where TAG_COLORS is consumed so all tag text meets 4.5:1 on the 40%
opacity background.


Comment on lines +34 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Move constant definition after all imports.

The TAG_COLORS constant is defined in the middle of the import statements, breaking the file's organization. Constants should be defined after all imports are complete.

♻️ Suggested fix

Move lines 34-46 to after line 58 (after all imports), before the interface definition:

 import { isIOSDevice } from "@/Utils/utils";
 
+const TAG_COLORS = [
+  "#B91C1C", // Red
+  "#C2410C", // Orange
+  "#A16207", // Amber
+  "#15803D", // Green
+  "#0F766E", // Teal
+  "#1D4ED8", // Blue
+  "#7E22CE", // Purple
+  "#BE185D", // Pink
+  "#0E7490", // Cyan
+  "#4D7C0F", // Lime
+] as const;
+
 interface TagConfigFormProps {

And remove lines 34-46 from their current location.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const TAG_COLORS = [
"#B91C1C", // Red
"#C2410C", // Orange
"#A16207", // Amber
"#15803D", // Green
"#0F766E", // Teal
"#1D4ED8", // Blue
"#7E22CE", // Purple
"#BE185D", // Pink
"#0E7490", // Cyan
"#4D7C0F", // Lime
] as const;
const TAG_COLORS = [
"#B91C1C", // Red
"#C2410C", // Orange
"#A16207", // Amber
"#15803D", // Green
"#0F766E", // Teal
"#1D4ED8", // Blue
"#7E22CE", // Purple
"#BE185D", // Pink
"#0E7490", // Cyan
"#4D7C0F", // Lime
] as const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Admin/TagConfig/TagConfigForm.tsx` around lines 34 - 46, The
TAG_COLORS constant is currently placed among the import statements; move the
complete TAG_COLORS definition (the const TAG_COLORS = [...] as const;) out of
the import block and place it after all imports are finished and before the
file's first type/interface or component definition (i.e., right before the
interface definition in this file), removing the original lines from the import
area so the file imports remain grouped at the top.

import useBreakpoints from "@/hooks/useBreakpoints";
import { cn } from "@/lib/utils";
Comment on lines +45 to 48
import {
Expand Down Expand Up @@ -76,6 +91,7 @@ export default function TagConfigForm({
}),
facility_organization: z.string().optional(),
organization: z.string().optional(),
color: z.string().optional(),
});
Comment on lines 92 to 95

type TagConfigFormValues = z.infer<typeof tagConfigSchema>;
Expand All @@ -101,6 +117,7 @@ export default function TagConfigForm({
resource: parentTag?.resource || TagResource.PATIENT,
facility_organization: undefined,
organization: undefined,
color: "",
},
});

Expand All @@ -126,6 +143,10 @@ export default function TagConfigForm({
resource: existingConfig.resource,
facility_organization: existingConfig.facility_organization?.id,
organization: existingConfig.organization?.id,
color:
typeof existingConfig.meta?.color === "string"
? existingConfig.meta.color
: "",
});
}
}, [existingConfig, isEditing, form]);
Expand All @@ -142,6 +163,7 @@ export default function TagConfigForm({
resource: parentTag.resource,
facility_organization: undefined,
organization: undefined,
color: "",
});
}
}, [parentTag, isCreatingChild, form]);
Expand Down Expand Up @@ -182,6 +204,7 @@ export default function TagConfigForm({
priority: data.priority,
status: data.status,
resource: data.resource,
meta: data.color ? { color: data.color } : {},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve existing metadata when building meta payload

onSubmit now always sends meta as either { color: ... } or {}; when editing a tag that already has other metadata keys in meta, this overwrites them and silently drops those values on save. This regression is introduced here because updates previously omitted meta entirely, so non-color metadata could be retained by the API. Please merge with existing metadata (or omit meta when unchanged) instead of replacing it wholesale.

Useful? React with 👍 / 👎.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what fields TagConfigMeta interface contains
ast-grep --pattern 'interface TagConfigMeta {
  $$$
}'

# Also check for type definition
rg -n "TagConfigMeta" --type=ts -A 5

Repository: ohcnetwork/care_fe

Length of output: 1320


🏁 Script executed:

sed -n '190,230p' src/pages/Admin/TagConfig/TagConfigForm.tsx

Repository: ohcnetwork/care_fe

Length of output: 1284


🏁 Script executed:

sed -n '1,210p' src/pages/Admin/TagConfig/TagConfigForm.tsx | head -100

Repository: ohcnetwork/care_fe

Length of output: 2925


🏁 Script executed:

sed -n '100,160p' src/pages/Admin/TagConfig/TagConfigForm.tsx

Repository: ohcnetwork/care_fe

Length of output: 2095


🏁 Script executed:

rg "meta\s*:\s*\{" --type=ts -B 2 -A 2 src/pages/Admin/TagConfig src/types/emr/tagConfig

Repository: ohcnetwork/care_fe

Length of output: 373


🏁 Script executed:

rg "\.meta\?" --type=ts src/pages/Admin/TagConfig src/types/emr/tagConfig -B 1 -A 1

Repository: ohcnetwork/care_fe

Length of output: 305


🏁 Script executed:

rg "meta:" --type=ts -A 3 src/types/emr/tagConfig/ | head -40

Repository: ohcnetwork/care_fe

Length of output: 510


🏁 Script executed:

rg "color" --type=ts src/pages/Admin/TagConfig -B 2 -A 2

Repository: ohcnetwork/care_fe

Length of output: 3606


Preserve existing meta fields when updating tags to prevent future data loss.

While currently only the color field is managed in meta, the open-ended TagConfigMeta type ([key: string]: unknown) could accommodate additional properties. The current payload construction at line 207 overwrites the entire meta object during updates, which would lose any other meta fields if they are added in the future.

Although this isn't causing data loss today, consider preserving existing meta fields for robustness:

Proposed fix to preserve existing meta
-      meta: data.color ? { color: data.color } : {},
+      meta: {
+        ...(isEditing && existingConfig?.meta ? existingConfig.meta : {}),
+        ...(data.color ? { color: data.color } : {}),
+      },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Admin/TagConfig/TagConfigForm.tsx` at line 207, The current update
payload sets meta to data.color ? { color: data.color } : {}, which overwrites
any existing TagConfigMeta fields; when building the payload in the
TagConfigForm submit/update handler (where meta is constructed), merge the
existing tag.meta (or the fetched tag object) with the new color instead of
replacing it — e.g., use a spread of existingMeta || {} combined with color only
when data.color is present so other meta keys are preserved; reference the meta
property, TagConfigMeta type, and data.color when making this change.

...(parentId && { parent: parentId }),
Comment on lines 204 to 208
...(facilityId && { facility: facilityId }),
...(data.facility_organization && {
Expand Down Expand Up @@ -358,6 +381,61 @@ export default function TagConfigForm({
)}
/>

<FormField
control={form.control}
name="color"
render={({ field }) => (
<FormItem>
<FormLabel>{t("color")}</FormLabel>
<FormControl>
<input type="hidden" {...field} value={field.value || ""} />
</FormControl>
<div className="flex items-center gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
const others = TAG_COLORS.filter((c) => c !== field.value);
field.onChange(
others[Math.floor(Math.random() * others.length)],
);
}}
disabled={isLoading}
>
<CareIcon icon="l-shuffle" className="size-4" />
{field.value ? t("shuffle_color") : t("assign_color")}
</Button>
{field.value && (
<>
<Badge
style={{
color: field.value,
backgroundColor: field.value + "18",
borderColor: field.value + "40",
}}
Comment on lines +412 to +416
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent opacity values with TagBadge component.

The badge preview uses different opacity suffixes (18 for background, 40 for border) compared to TagBadge which uses 40 for background and 60 for border. This creates a visual inconsistency between the preview and the actual rendered tag.

Based on the relevant code snippet from TagBadge.tsx, the color styling should match:

🔧 Proposed fix for consistency
                     <Badge
                       style={{
                         color: field.value,
-                        backgroundColor: field.value + "18",
-                        borderColor: field.value + "40",
+                        backgroundColor: field.value + "40",
+                        borderColor: field.value + "60",
                       }}
                       className="capitalize"
                     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
style={{
color: field.value,
backgroundColor: field.value + "18",
borderColor: field.value + "40",
}}
<Badge
style={{
color: field.value,
backgroundColor: field.value + "40",
borderColor: field.value + "60",
}}
className="capitalize"
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Admin/TagConfig/TagConfigForm.tsx` around lines 412 - 416, The tag
preview in TagConfigForm uses inconsistent opacity suffixes (background: "18",
border: "40") compared to TagBadge; update the inline style in the preview
(where style is set using field.value) to use the same opacity pattern as
TagBadge—set backgroundColor to field.value + "40" and borderColor to
field.value + "60" so the preview matches the TagBadge component's rendering.

className="capitalize"
>
{form.watch("display") || t("preview")}
</Badge>
<Button
type="button"
variant="ghost"
size="icon"
className="size-7 text-muted-foreground"
onClick={() => field.onChange("")}
disabled={isLoading}
>
<CareIcon icon="l-times" className="size-4" />
</Button>
Comment on lines +421 to +430
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add accessible label to icon-only clear button.

The clear button uses only an icon without visible text or an accessible label, making it inaccessible to screen reader users. As per coding guidelines, proper ARIA labels should be implemented for accessibility.

♿ Proposed fix
                     <Button
                       type="button"
                       variant="ghost"
                       size="icon"
                       className="size-7 text-muted-foreground"
                       onClick={() => field.onChange("")}
                       disabled={isLoading}
+                      aria-label={t("clear_color")}
                     >
                       <CareIcon icon="l-times" className="size-4" />
                     </Button>

Note: You may need to add the clear_color translation key to the locale files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Admin/TagConfig/TagConfigForm.tsx` around lines 421 - 430, The
clear Button (the icon-only button rendering CareIcon inside) lacks an
accessible label; update the Button in TagConfigForm.tsx to include an
aria-label (or aria-labelledby) using the translation key (e.g., "clear_color")
and ensure the translation key is added to locale files; keep existing props
(type, variant, size, onClick, disabled) and retain CareIcon for visual display
while providing the aria-label so screen readers announce the button purpose.

</>
)}
</div>
<FormMessage />
</FormItem>
)}
/>

{facilityId ? (
<FormField
control={form.control}
Expand Down
1 change: 1 addition & 0 deletions src/types/emr/tagConfig/tagConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface TagConfigRequest {
organization?: string;
facility?: string;
facility_organization?: string;
meta?: TagConfigMeta;
}

export function getTagHierarchyDisplay(
Expand Down
Loading