Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,26 @@
border-radius: 16px;
}

.toggleWrapper {
display: flex;
align-items: center;
gap: $sp-2;
cursor: pointer;
padding: 4px $sp-2;
border-radius: $sp-1;
transition: background-color 0.2s ease;

&:hover {
background-color: rgba($black-100, 0.05);
}
}

.toggleLabel {
font-size: 12px;
font-weight: 500;
color: $black-60;
}

.workflowsTree {
padding: $sp-3 $sp-4;
background-color: $tc-white;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ChangeEvent, FC, useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify'
import _ from 'lodash'

import { BaseModal, Button, IconOutline, InputSelect, InputSelectOption } from '~/libs/ui'
import { BaseModal, Button, FormToggleSwitch, IconOutline, InputSelect, InputSelectOption } from '~/libs/ui'

import { PageWrapper, TableLoading, TableNoRecord } from '../../lib'
import { ConfirmModal, PageWrapper, TableLoading, TableNoRecord } from '../../lib'
import { TableWrapper } from '../../lib/components/common/TableWrapper'
import { ChallengeTrack, ChallengeType } from '../../lib/models'
import { getChallengeTracks, getChallengeTypes } from '../../lib/services/challenge-management.service'
Expand All @@ -13,6 +15,7 @@ import {
deleteAiReviewTemplate,
getAiReviewTemplates,
TemplateWorkflowItem,
updateAiReviewTemplate,
} from '../../lib/services/ai-templates.service'
import { WorkflowDetailsModal } from '../review-workflows/WorkflowDetailsModal'

Expand Down Expand Up @@ -67,6 +70,7 @@ interface TemplateItemProps {
onWorkflowClick: (workflow: AiWorkflow) => void
onEdit: (template: AiReviewTemplate) => void
onDelete: (template: AiReviewTemplate) => void
onToggleDisabled: (template: AiReviewTemplate) => void
}

const TemplateItem: FC<TemplateItemProps> = (props: TemplateItemProps) => {
Expand All @@ -87,6 +91,11 @@ const TemplateItem: FC<TemplateItemProps> = (props: TemplateItemProps) => {
props.onDelete(props.template)
}, [props])

const handleToggleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
props.onToggleDisabled(props.template)
}, [props])

return (
<div className={styles.templateItem}>
<div
Expand All @@ -103,9 +112,16 @@ const TemplateItem: FC<TemplateItemProps> = (props: TemplateItemProps) => {
</span>
</div>
<div className={styles.templateHeaderRight}>
{props.template.disabled && (
<span className={styles.disabledBadge}>Disabled</span>
)}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<div className={styles.toggleWrapper} onClick={handleToggleClick}>
<span className={styles.toggleLabel}>Active</span>
<FormToggleSwitch
name={`active-${props.template.id}`}
value={!props.template.disabled}
onChange={_.noop}
/>
</div>
<button
type='button'
className={styles.editButton}
Expand Down Expand Up @@ -174,6 +190,10 @@ export const AiReviewTemplatesPage: FC = () => {
open: false,
})
const [isDeleting, setIsDeleting] = useState(false)
const [toggleModal, setToggleModal] = useState<{ open: boolean; template?: AiReviewTemplate }>({
open: false,
})
const [isToggling, setIsToggling] = useState(false)

const trackOptions: InputSelectOption[] = useMemo(() => {
const seen = new Set<string>()
Expand All @@ -193,9 +213,9 @@ export const AiReviewTemplatesPage: FC = () => {
const seen = new Set<string>()
const options: InputSelectOption[] = [{ label: 'All Types', value: '' }]
for (const t of types) {
if (!seen.has(t.abbreviation)) {
seen.add(t.abbreviation)
options.push({ label: t.name, value: t.abbreviation })
if (!seen.has(t.name)) {
seen.add(t.name)
options.push({ label: t.name, value: t.name })
}
}

Expand Down Expand Up @@ -288,6 +308,36 @@ export const AiReviewTemplatesPage: FC = () => {
}
}, [deleteModal.template, filter, loadTemplates])

const handleToggleClick = useCallback((template: AiReviewTemplate) => {
setToggleModal({ open: true, template })
}, [])

const handleCloseToggleModal = useCallback(() => {
setToggleModal({ open: false })
}, [])

const handleConfirmToggle = useCallback(async () => {
if (!toggleModal.template) return
setIsToggling(true)
const newDisabledState = !toggleModal.template.disabled
try {
await updateAiReviewTemplate(toggleModal.template.id, {
disabled: newDisabledState,
})
setTemplates(prev => prev.map(t => (
t.id === toggleModal.template?.id
? { ...t, disabled: newDisabledState }
: t
)))
toast.success(`Template ${newDisabledState ? 'deactivated' : 'activated'} successfully`)
setToggleModal({ open: false })
} catch (error) {
toast.error('Failed to update template')
} finally {
setIsToggling(false)
}
}, [toggleModal.template])

const hasFilters: boolean = !!filter.challengeTrack || !!filter.challengeType

return (
Expand Down Expand Up @@ -344,6 +394,7 @@ export const AiReviewTemplatesPage: FC = () => {
onWorkflowClick={handleWorkflowClick}
onEdit={handleEditClick}
onDelete={handleDeleteClick}
onToggleDisabled={handleToggleClick}
/>
))}
</div>
Expand Down Expand Up @@ -394,6 +445,26 @@ export const AiReviewTemplatesPage: FC = () => {
{deleteModal.template?.title || deleteModal.template?.id}
&quot;? This action cannot be undone.
</BaseModal>

<ConfirmModal
title='Confirm Status Change'
action={toggleModal.template?.disabled ? 'Activate' : 'Deactivate'}
open={toggleModal.open}
onClose={handleCloseToggleModal}
onConfirm={handleConfirmToggle}
isLoading={isToggling}
>
<p>
Are you sure you want to
{' '}
<strong>{toggleModal.template?.disabled ? 'activate' : 'deactivate'}</strong>
{' '}
the template
{' '}
<strong>{toggleModal.template?.title || toggleModal.template?.id}</strong>
?
</p>
</ConfirmModal>
</PageWrapper>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
border: 1px solid $black-20;
border-radius: $sp-2;
padding: $sp-4;
overflow: visible;
}

.workflowsHeader {
Expand Down Expand Up @@ -77,6 +78,11 @@
:global(.input-select-react__control) {
min-height: 40px;
}

// Ensure dropdown menu appears above modal content
:global(.input-select-react__menu) {
z-index: 9999;
}
}

.weightInput {
Expand Down
52 changes: 39 additions & 13 deletions src/apps/admin/src/ai/review-templates/CreateTemplateModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,18 +184,32 @@ export const CreateTemplateModal: FC<Props> = (props: Props) => {
const append = fieldArrayResult.append
const remove = fieldArrayResult.remove

const trackOptions: InputSelectOption[] = useMemo(() => [
{ label: 'Select track', value: '' },
...tracks.map(t => {
const trackOptions: InputSelectOption[] = useMemo(() => {
const seen = new Set<string>()
const options: InputSelectOption[] = [{ label: 'Select track', value: '' }]
for (const t of tracks) {
const trackValue: string = (t as ChallengeTrack & { track?: string }).track || t.name.toUpperCase()
return { label: t.name, value: trackValue }
}),
], [tracks])
if (!seen.has(trackValue)) {
seen.add(trackValue)
options.push({ label: t.name, value: trackValue })
}
}

return options
}, [tracks])

const typeOptions: InputSelectOption[] = useMemo(() => {
const seen = new Set<string>()
const options: InputSelectOption[] = [{ label: 'Select type', value: '' }]
for (const t of types) {
if (!seen.has(t.name)) {
seen.add(t.name)
options.push({ label: t.name, value: t.name })
}
}

const typeOptions: InputSelectOption[] = useMemo(() => [
{ label: 'Select type', value: '' },
...types.map(t => ({ label: t.name, value: t.abbreviation })),
], [types])
return options
}, [types])

const workflowOptions: InputSelectOption[] = useMemo(() => workflows
.filter(w => !w.disabled)
Expand Down Expand Up @@ -245,6 +259,16 @@ export const CreateTemplateModal: FC<Props> = (props: Props) => {
append({ isGating: false, weightPercent: 100, workflowId: '' })
}, [append])

const onError = useCallback((formErrors: typeof errors) => {
const workflowError = formErrors.workflows?.message
|| formErrors.workflows?.root?.message
if (workflowError) {
toast.error(workflowError as string)
} else {
toast.error('Please fix the validation errors before submitting')
}
}, [])

const onSubmit = useCallback((data: FormValues) => {
setIsSubmitting(true)

Expand Down Expand Up @@ -312,7 +336,7 @@ export const CreateTemplateModal: FC<Props> = (props: Props) => {
) : (
<form
className={styles.container}
onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(onSubmit, onError)}
>
<div className={styles.formGrid}>
<InputText
Expand Down Expand Up @@ -475,8 +499,10 @@ export const CreateTemplateModal: FC<Props> = (props: Props) => {
</Button>
</div>

{errors.workflows && typeof errors.workflows.message === 'string' && (
<p className={styles.errorText}>{errors.workflows.message}</p>
{(errors.workflows?.message || errors.workflows?.root?.message) && (
<p className={styles.errorText}>
{(errors.workflows?.message || errors.workflows?.root?.message) as string}
</p>
)}

{fields.map((field, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,26 @@

tbody tr {
td {
padding: $sp-2 $sp-4 !important;
padding: $sp-2 $sp-3 !important;
border-radius: 0 !important;
background-color: $tc-white !important;
vertical-align: middle;

&:first-child {
font-weight: 600;
font-size: 12px;
font-size: 11px;
text-transform: uppercase;
color: $black-60;
white-space: nowrap;
width: 120px;
width: 100px;
min-width: 100px;
max-width: 100px;
}

&:last-child {
word-break: break-all;
overflow-wrap: anywhere;
text-align: right;
}
}

Expand All @@ -38,10 +47,26 @@
background-color: $black-5 !important;
}
}

@media (max-width: 400px) {
tbody tr td {
padding: $sp-1 $sp-2 !important;
font-size: 13px;

&:first-child {
font-size: 10px;
width: 85px;
min-width: 85px;
max-width: 85px;
}
}
}
}

.link {
color: $link-blue-dark;
font-size: 14px;
font-weight: 400;
text-decoration: none;
cursor: pointer;

Expand All @@ -55,6 +80,13 @@
text-overflow: ellipsis;
white-space: nowrap;
max-width: 250px;

@media (max-width: 1050px) {
white-space: normal;
word-break: break-all;
overflow-wrap: anywhere;
max-width: none;
}
}

.nameCell {
Expand All @@ -67,6 +99,8 @@
border: none;
padding: 0;
color: $link-blue-dark;
font-size: 14px;
font-weight: 400;
text-decoration: none;
cursor: pointer;
text-align: left;
Expand All @@ -76,6 +110,12 @@
&:hover {
text-decoration: underline;
}

@media (max-width: 1050px) {
text-align: right;
display: block;
width: 100%;
}
}

.toggle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ export const AiReviewWorkflowsPage: FC = () => {
type: 'element',
},
{
defaultSortDirection: 'asc',
label: 'Scorecard',
propertyName: 'scorecard.name',
renderer: (data: AiWorkflow) => {
if (!data.scorecard?.id) {
return <span>{data.scorecard?.name || 'N/A'}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@

.sel {
&:global(__menu-portal).sel:global(__menu-portal) {
z-index: 1001;
z-index: 10001;
}
&:global(__menu) {
width: 100%;
Expand Down
Loading
Loading