Feat: Use-case settings layout#2663
Conversation
WalkthroughThe ProjectSettings page undergoes a UI restructuring, introducing a centralized styles hook (Material-UI based) and reorganizing the form layout into Card-based sections (General Information, Team & Compliance). Field arrangements, styling, and component composition are updated while preserving existing functionality for owner management, risk classification, team member handling, and framework operations. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Clients/src/presentation/pages/ProjectView/ProjectSettings/index.tsx(3 hunks)Clients/src/presentation/pages/ProjectView/ProjectSettings/styles.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-11-18T05:28:01.115Z
Learnt from: MuhammadKhalilzadeh
Repo: bluewave-labs/verifywise PR: 254
File: Clients/src/presentation/components/Modals/Controlpane/index.tsx:22-24
Timestamp: 2024-11-18T05:28:01.115Z
Learning: In `Clients/src/presentation/components/Modals/Controlpane/index.tsx` (TypeScript React component), avoid changing prop types from specific types like `string` to `any` when the exact type is known. Maintaining explicit types ensures better type safety and code reliability.
Applied to files:
Clients/src/presentation/pages/ProjectView/ProjectSettings/styles.tsClients/src/presentation/pages/ProjectView/ProjectSettings/index.tsx
📚 Learning: 2024-10-16T16:18:22.983Z
Learnt from: sankettank66
Repo: bluewave-labs/verifywise PR: 94
File: Clients/src/presentation/pages/Authentication/Login/index.tsx:91-108
Timestamp: 2024-10-16T16:18:22.983Z
Learning: As per project requirements, client-side validation should not be added to the authentication views, including `Clients/src/presentation/pages/Authentication/Login/index.tsx` and similar components.
Applied to files:
Clients/src/presentation/pages/ProjectView/ProjectSettings/index.tsx
🧬 Code graph analysis (1)
Clients/src/presentation/pages/ProjectView/ProjectSettings/index.tsx (2)
Clients/src/presentation/pages/ProjectView/ProjectSettings/styles.ts (1)
useStyles(3-36)Clients/src/presentation/utils/inputStyles.ts (1)
getAutocompleteStyles(183-240)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
| {monitoredFrameworks.length > 0 && ( | ||
| <> | ||
| {/* Applicable regulations Row */} | ||
| <Box> | ||
| <Typography sx={{ fontSize: 13, fontWeight: 500 }}> | ||
| Applicable regulations * | ||
| </Typography> | ||
| <Typography | ||
| sx={{ | ||
| fontSize: "13px", | ||
| color: isComingSoon | ||
| ? "text.secondary" | ||
| : "text.primary", | ||
| }} | ||
| sx={{ fontSize: 12, color: "#888", whiteSpace: "nowrap" }} | ||
| > | ||
| {option.name} | ||
| </Typography> | ||
| Add all monitored regulations and standards of the use case. | ||
| </Typography> | ||
| </Box> | ||
| ); | ||
| }} | ||
| isOptionEqualToValue={( | ||
| option: { _id: number }, | ||
| value: { _id: number }, | ||
| ) => option._id === value._id} | ||
| getOptionDisabled={(option: { name: string }) => | ||
| option.name.includes("coming soon") | ||
| } | ||
| filterSelectedOptions | ||
| popupIcon={ | ||
| <ChevronDown | ||
| size={16} | ||
| color={theme.palette.text.tertiary} | ||
| /> | ||
| } | ||
| renderInput={(params) => ( | ||
| <TextField | ||
| {...params} | ||
| placeholder="Select regulations and standards" | ||
| sx={{ | ||
| "& .MuiOutlinedInput-root": { | ||
| height: "34px", | ||
| padding: "0 10px", | ||
| display: "flex", | ||
| alignItems: "center", | ||
| }, | ||
| "& .MuiInputBase-root": { | ||
| height: "34px !important", | ||
| padding: "0 10px !important", | ||
| display: "flex !important", | ||
| alignItems: "center !important", | ||
| justifyContent: "flex-start !important", | ||
| }, | ||
| "& .MuiInputBase-input": { | ||
| padding: "0 !important", | ||
| margin: "0 !important", | ||
| fontSize: "13px", | ||
| lineHeight: "1 !important", | ||
| }, | ||
| "& ::placeholder": { | ||
| fontSize: "13px", | ||
| }, | ||
| }} | ||
| /> | ||
| <Stack> | ||
| <Autocomplete | ||
| multiple | ||
| id="monitored-regulations-and-standards-input" | ||
| size="small" | ||
| value={values.monitoredRegulationsAndStandards} | ||
| options={nonOrganizationalFrameworks.map((fw: Framework) => ({ | ||
| _id: Number(fw.id), | ||
| name: fw.name, | ||
| }))} | ||
| onChange={handleOnMultiSelect( | ||
| "monitoredRegulationsAndStandards", | ||
| )} | ||
| getOptionLabel={(item: { _id: number; name: string }) => | ||
| item.name | ||
| } | ||
| noOptionsText={ | ||
| values.monitoredRegulationsAndStandards.length === | ||
| nonOrganizationalFrameworks.length | ||
| ? "All regulations selected" | ||
| : "No options" | ||
| } | ||
| renderOption={( | ||
| props: any, | ||
| option: { _id: number; name: string }, | ||
| ) => { | ||
| const isComingSoon = option.name.includes("coming soon"); | ||
| return ( | ||
| <Box | ||
| component="li" | ||
| {...props} | ||
| sx={{ | ||
| opacity: isComingSoon ? 0.5 : 1, | ||
| cursor: isComingSoon ? "not-allowed" : "pointer", | ||
| "&:hover": { | ||
| backgroundColor: isComingSoon | ||
| ? "transparent" | ||
| : undefined, | ||
| }, | ||
| }} | ||
| > | ||
| <Typography | ||
| sx={{ | ||
| fontSize: "13px", | ||
| color: isComingSoon | ||
| ? "text.secondary" | ||
| : "text.primary", | ||
| }} | ||
| > | ||
| {option.name} | ||
| </Typography> | ||
| </Box> | ||
| ); | ||
| }} | ||
| isOptionEqualToValue={( | ||
| option: { _id: number }, | ||
| value: { _id: number }, | ||
| ) => option._id === value._id} | ||
| getOptionDisabled={(option: { name: string }) => | ||
| option.name.includes("coming soon") | ||
| } | ||
| filterSelectedOptions | ||
| popupIcon={ | ||
| <ChevronDown | ||
| size={16} | ||
| color={theme.palette.text.tertiary} | ||
| /> | ||
| } | ||
| renderInput={(params) => ( | ||
| <TextField | ||
| {...params} | ||
| placeholder="Select regulations and standards" | ||
| sx={{ | ||
| "& .MuiOutlinedInput-root": { | ||
| height: "34px", | ||
| padding: "0 10px", | ||
| display: "flex", | ||
| alignItems: "center", | ||
| }, | ||
| "& .MuiInputBase-root": { | ||
| height: "34px !important", | ||
| padding: "0 10px !important", | ||
| display: "flex !important", | ||
| alignItems: "center !important", | ||
| justifyContent: "flex-start !important", | ||
| }, | ||
| "& .MuiInputBase-input": { | ||
| padding: "0 !important", | ||
| margin: "0 !important", | ||
| fontSize: "13px", | ||
| lineHeight: "1 !important", | ||
| }, | ||
| "& ::placeholder": { | ||
| fontSize: "13px", | ||
| }, | ||
| }} | ||
| /> | ||
| )} | ||
| sx={{ | ||
| ...getAutocompleteStyles(theme, { hasError: !!errors.monitoredRegulationsAndStandards }), | ||
| width: "400px", | ||
| backgroundColor: theme.palette.background.main, | ||
| ".MuiAutocomplete-clearIndicator": { | ||
| display: "none", | ||
| }, | ||
| "& .MuiOutlinedInput-root": { | ||
| ...getAutocompleteStyles(theme, { hasError: !!errors.monitoredRegulationsAndStandards })["& .MuiOutlinedInput-root"], | ||
| borderRadius: "4px", | ||
| }, | ||
| "& .MuiChip-root": { | ||
| borderRadius: "4px", | ||
| "& .MuiChip-deleteIcon": { | ||
| display: | ||
| values.monitoredRegulationsAndStandards.length === 1 | ||
| ? "none" | ||
| : "flex", | ||
| }, | ||
| }, | ||
| }} | ||
| slotProps={{ | ||
| paper: { | ||
| sx: { | ||
| "& .MuiAutocomplete-listbox": { | ||
| "& .MuiAutocomplete-option": { | ||
| fontSize: "13px", | ||
| color: "#1c2130", | ||
| paddingLeft: "9px", | ||
| paddingRight: "9px", | ||
| }, | ||
| "& .MuiAutocomplete-option.Mui-focused": { | ||
| background: "#f9fafb", | ||
| }, | ||
| }, | ||
| "& .MuiAutocomplete-noOptions": { | ||
| fontSize: "13px", | ||
| paddingLeft: "9px", | ||
| paddingRight: "9px", | ||
| }, | ||
| }, | ||
| }, | ||
| }} | ||
| /> | ||
| {removedFramework && | ||
| values.monitoredRegulationsAndStandards.length === 1 && ( | ||
| <Typography | ||
| variant="caption" | ||
| sx={{ color: "warning.main", fontWeight: 300, mt: 1 }} | ||
| > | ||
| Framework cannot be empty. | ||
| </Typography> | ||
| )} | ||
| </Stack> | ||
| </> | ||
| )} |
There was a problem hiding this comment.
Do not hide the frameworks selector when none are linked yet.
Wrapping the entire “Applicable regulations” block in monitoredFrameworks.length > 0 means the selector disappears as soon as a project has zero linked frameworks (for example, a brand-new use case). With the field hidden there is no path to add the first framework, so the feature is broken for exactly the scenario this flow must support. Please render the section whenever frameworks can be chosen, regardless of the current selection state. A simple fix is to drop the guard (or gate on the availability of options instead).
- {monitoredFrameworks.length > 0 && (
- <>
+ <>
{/* Applicable regulations Row */}
<Box>
<Typography sx={{ fontSize: 13, fontWeight: 500 }}>
Applicable regulations *
</Typography>
...
</>
- )}
+ </>📝 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.
| {monitoredFrameworks.length > 0 && ( | |
| <> | |
| {/* Applicable regulations Row */} | |
| <Box> | |
| <Typography sx={{ fontSize: 13, fontWeight: 500 }}> | |
| Applicable regulations * | |
| </Typography> | |
| <Typography | |
| sx={{ | |
| fontSize: "13px", | |
| color: isComingSoon | |
| ? "text.secondary" | |
| : "text.primary", | |
| }} | |
| sx={{ fontSize: 12, color: "#888", whiteSpace: "nowrap" }} | |
| > | |
| {option.name} | |
| </Typography> | |
| Add all monitored regulations and standards of the use case. | |
| </Typography> | |
| </Box> | |
| ); | |
| }} | |
| isOptionEqualToValue={( | |
| option: { _id: number }, | |
| value: { _id: number }, | |
| ) => option._id === value._id} | |
| getOptionDisabled={(option: { name: string }) => | |
| option.name.includes("coming soon") | |
| } | |
| filterSelectedOptions | |
| popupIcon={ | |
| <ChevronDown | |
| size={16} | |
| color={theme.palette.text.tertiary} | |
| /> | |
| } | |
| renderInput={(params) => ( | |
| <TextField | |
| {...params} | |
| placeholder="Select regulations and standards" | |
| sx={{ | |
| "& .MuiOutlinedInput-root": { | |
| height: "34px", | |
| padding: "0 10px", | |
| display: "flex", | |
| alignItems: "center", | |
| }, | |
| "& .MuiInputBase-root": { | |
| height: "34px !important", | |
| padding: "0 10px !important", | |
| display: "flex !important", | |
| alignItems: "center !important", | |
| justifyContent: "flex-start !important", | |
| }, | |
| "& .MuiInputBase-input": { | |
| padding: "0 !important", | |
| margin: "0 !important", | |
| fontSize: "13px", | |
| lineHeight: "1 !important", | |
| }, | |
| "& ::placeholder": { | |
| fontSize: "13px", | |
| }, | |
| }} | |
| /> | |
| <Stack> | |
| <Autocomplete | |
| multiple | |
| id="monitored-regulations-and-standards-input" | |
| size="small" | |
| value={values.monitoredRegulationsAndStandards} | |
| options={nonOrganizationalFrameworks.map((fw: Framework) => ({ | |
| _id: Number(fw.id), | |
| name: fw.name, | |
| }))} | |
| onChange={handleOnMultiSelect( | |
| "monitoredRegulationsAndStandards", | |
| )} | |
| getOptionLabel={(item: { _id: number; name: string }) => | |
| item.name | |
| } | |
| noOptionsText={ | |
| values.monitoredRegulationsAndStandards.length === | |
| nonOrganizationalFrameworks.length | |
| ? "All regulations selected" | |
| : "No options" | |
| } | |
| renderOption={( | |
| props: any, | |
| option: { _id: number; name: string }, | |
| ) => { | |
| const isComingSoon = option.name.includes("coming soon"); | |
| return ( | |
| <Box | |
| component="li" | |
| {...props} | |
| sx={{ | |
| opacity: isComingSoon ? 0.5 : 1, | |
| cursor: isComingSoon ? "not-allowed" : "pointer", | |
| "&:hover": { | |
| backgroundColor: isComingSoon | |
| ? "transparent" | |
| : undefined, | |
| }, | |
| }} | |
| > | |
| <Typography | |
| sx={{ | |
| fontSize: "13px", | |
| color: isComingSoon | |
| ? "text.secondary" | |
| : "text.primary", | |
| }} | |
| > | |
| {option.name} | |
| </Typography> | |
| </Box> | |
| ); | |
| }} | |
| isOptionEqualToValue={( | |
| option: { _id: number }, | |
| value: { _id: number }, | |
| ) => option._id === value._id} | |
| getOptionDisabled={(option: { name: string }) => | |
| option.name.includes("coming soon") | |
| } | |
| filterSelectedOptions | |
| popupIcon={ | |
| <ChevronDown | |
| size={16} | |
| color={theme.palette.text.tertiary} | |
| /> | |
| } | |
| renderInput={(params) => ( | |
| <TextField | |
| {...params} | |
| placeholder="Select regulations and standards" | |
| sx={{ | |
| "& .MuiOutlinedInput-root": { | |
| height: "34px", | |
| padding: "0 10px", | |
| display: "flex", | |
| alignItems: "center", | |
| }, | |
| "& .MuiInputBase-root": { | |
| height: "34px !important", | |
| padding: "0 10px !important", | |
| display: "flex !important", | |
| alignItems: "center !important", | |
| justifyContent: "flex-start !important", | |
| }, | |
| "& .MuiInputBase-input": { | |
| padding: "0 !important", | |
| margin: "0 !important", | |
| fontSize: "13px", | |
| lineHeight: "1 !important", | |
| }, | |
| "& ::placeholder": { | |
| fontSize: "13px", | |
| }, | |
| }} | |
| /> | |
| )} | |
| sx={{ | |
| ...getAutocompleteStyles(theme, { hasError: !!errors.monitoredRegulationsAndStandards }), | |
| width: "400px", | |
| backgroundColor: theme.palette.background.main, | |
| ".MuiAutocomplete-clearIndicator": { | |
| display: "none", | |
| }, | |
| "& .MuiOutlinedInput-root": { | |
| ...getAutocompleteStyles(theme, { hasError: !!errors.monitoredRegulationsAndStandards })["& .MuiOutlinedInput-root"], | |
| borderRadius: "4px", | |
| }, | |
| "& .MuiChip-root": { | |
| borderRadius: "4px", | |
| "& .MuiChip-deleteIcon": { | |
| display: | |
| values.monitoredRegulationsAndStandards.length === 1 | |
| ? "none" | |
| : "flex", | |
| }, | |
| }, | |
| }} | |
| slotProps={{ | |
| paper: { | |
| sx: { | |
| "& .MuiAutocomplete-listbox": { | |
| "& .MuiAutocomplete-option": { | |
| fontSize: "13px", | |
| color: "#1c2130", | |
| paddingLeft: "9px", | |
| paddingRight: "9px", | |
| }, | |
| "& .MuiAutocomplete-option.Mui-focused": { | |
| background: "#f9fafb", | |
| }, | |
| }, | |
| "& .MuiAutocomplete-noOptions": { | |
| fontSize: "13px", | |
| paddingLeft: "9px", | |
| paddingRight: "9px", | |
| }, | |
| }, | |
| }, | |
| }} | |
| /> | |
| {removedFramework && | |
| values.monitoredRegulationsAndStandards.length === 1 && ( | |
| <Typography | |
| variant="caption" | |
| sx={{ color: "warning.main", fontWeight: 300, mt: 1 }} | |
| > | |
| Framework cannot be empty. | |
| </Typography> | |
| )} | |
| </Stack> | |
| </> | |
| )} | |
| <> | |
| {/* Applicable regulations Row */} | |
| <Box> | |
| <Typography sx={{ fontSize: 13, fontWeight: 500 }}> | |
| Applicable regulations * | |
| </Typography> | |
| <Typography | |
| sx={{ fontSize: 12, color: "#888", whiteSpace: "nowrap" }} | |
| > | |
| Add all monitored regulations and standards of the use case. | |
| </Typography> | |
| </Box> | |
| <Stack> | |
| <Autocomplete | |
| multiple | |
| id="monitored-regulations-and-standards-input" | |
| size="small" | |
| value={values.monitoredRegulationsAndStandards} | |
| options={nonOrganizationalFrameworks.map((fw: Framework) => ({ | |
| _id: Number(fw.id), | |
| name: fw.name, | |
| }))} | |
| onChange={handleOnMultiSelect( | |
| "monitoredRegulationsAndStandards", | |
| )} | |
| getOptionLabel={(item: { _id: number; name: string }) => | |
| item.name | |
| } | |
| noOptionsText={ | |
| values.monitoredRegulationsAndStandards.length === | |
| nonOrganizationalFrameworks.length | |
| ? "All regulations selected" | |
| : "No options" | |
| } | |
| renderOption={( | |
| props: any, | |
| option: { _id: number; name: string }, | |
| ) => { | |
| const isComingSoon = option.name.includes("coming soon"); | |
| return ( | |
| <Box | |
| component="li" | |
| {...props} | |
| sx={{ | |
| opacity: isComingSoon ? 0.5 : 1, | |
| cursor: isComingSoon ? "not-allowed" : "pointer", | |
| "&:hover": { | |
| backgroundColor: isComingSoon | |
| ? "transparent" | |
| : undefined, | |
| }, | |
| }} | |
| > | |
| <Typography | |
| sx={{ | |
| fontSize: "13px", | |
| color: isComingSoon | |
| ? "text.secondary" | |
| : "text.primary", | |
| }} | |
| > | |
| {option.name} | |
| </Typography> | |
| </Box> | |
| ); | |
| }} | |
| isOptionEqualToValue={( | |
| option: { _id: number }, | |
| value: { _id: number }, | |
| ) => option._id === value._id} | |
| getOptionDisabled={(option: { name: string }) => | |
| option.name.includes("coming soon") | |
| } | |
| filterSelectedOptions | |
| popupIcon={ | |
| <ChevronDown | |
| size={16} | |
| color={theme.palette.text.tertiary} | |
| /> | |
| } | |
| renderInput={(params) => ( | |
| <TextField | |
| {...params} | |
| placeholder="Select regulations and standards" | |
| sx={{ | |
| "& .MuiOutlinedInput-root": { | |
| height: "34px", | |
| padding: "0 10px", | |
| display: "flex", | |
| alignItems: "center", | |
| }, | |
| "& .MuiInputBase-root": { | |
| height: "34px !important", | |
| padding: "0 10px !important", | |
| display: "flex !important", | |
| alignItems: "center !important", | |
| justifyContent: "flex-start !important", | |
| }, | |
| "& .MuiInputBase-input": { | |
| padding: "0 !important", | |
| margin: "0 !important", | |
| fontSize: "13px", | |
| lineHeight: "1 !important", | |
| }, | |
| "& ::placeholder": { | |
| fontSize: "13px", | |
| }, | |
| }} | |
| /> | |
| )} | |
| sx={{ | |
| ...getAutocompleteStyles(theme, { hasError: !!errors.monitoredRegulationsAndStandards }), | |
| width: "400px", | |
| backgroundColor: theme.palette.background.main, | |
| ".MuiAutocomplete-clearIndicator": { | |
| display: "none", | |
| }, | |
| "& .MuiOutlinedInput-root": { | |
| ...getAutocompleteStyles(theme, { hasError: !!errors.monitoredRegulationsAndStandards })["& .MuiOutlinedInput-root"], | |
| borderRadius: "4px", | |
| }, | |
| "& .MuiChip-root": { | |
| borderRadius: "4px", | |
| "& .MuiChip-deleteIcon": { | |
| display: | |
| values.monitoredRegulationsAndStandards.length === 1 | |
| ? "none" | |
| : "flex", | |
| }, | |
| }, | |
| }} | |
| slotProps={{ | |
| paper: { | |
| sx: { | |
| "& .MuiAutocomplete-listbox": { | |
| "& .MuiAutocomplete-option": { | |
| fontSize: "13px", | |
| color: "#1c2130", | |
| paddingLeft: "9px", | |
| paddingRight: "9px", | |
| }, | |
| "& .MuiAutocomplete-option.Mui-focused": { | |
| background: "#f9fafb", | |
| }, | |
| }, | |
| "& .MuiAutocomplete-noOptions": { | |
| fontSize: "13px", | |
| paddingLeft: "9px", | |
| paddingRight: "9px", | |
| }, | |
| }, | |
| }, | |
| }} | |
| /> | |
| {removedFramework && | |
| values.monitoredRegulationsAndStandards.length === 1 && ( | |
| <Typography | |
| variant="caption" | |
| sx={{ color: "warning.main", fontWeight: 300, mt: 1 }} | |
| > | |
| Framework cannot be empty. | |
| </Typography> | |
| )} | |
| </Stack> | |
| </> |
🤖 Prompt for AI Agents
In Clients/src/presentation/pages/ProjectView/ProjectSettings/index.tsx around
lines 1041 to 1207, the entire "Applicable regulations" block is conditionally
rendered only when monitoredFrameworks.length > 0 which hides the selector for
projects with zero linked frameworks and prevents adding the first framework;
remove that monitoredFrameworks.length > 0 guard and instead always render the
"Applicable regulations" section (or gate its rendering on the availability of
nonOrganizationalFrameworks options if needed), so the Autocomplete and
explanatory text are visible even when no frameworks are currently selected.
|
Thanks. LGTM |
MuhammadKhalilzadeh
left a comment
There was a problem hiding this comment.
Looks great, thank you @Br0wnHammer
Describe your changes
This PR implements the new Use-case settings layout.
Fixes #2424
Please ensure all items are checked off before requesting a review: