UI/UX audit fixes: clickable pipelines, broken links, isActive locking
Build and Push Docker Image / build (push) Successful in 10m55s
Details
Build and Push Docker Image / build (push) Successful in 10m55s
Details
- Make pipeline cards clickable on list page (navigate to detail view) - Fix broken nav link: applicant /messages → /mentor - Fix broken nav link: mentor /messages → /projects - Add isActive field locking to all 7 wizard sections (intake, main-track, filtering, assignment, awards, live-finals, notifications) - Add minLoad ≤ maxLoad cross-field validation in assignment section - Add duplicate stage slug detection in main track section - Add active pipeline warning banners in intake and main track sections Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
31225b099e
commit
7d1c87e938
|
|
@ -309,6 +309,7 @@ export default function EditPipelinePage() {
|
|||
onChange={(c) =>
|
||||
updateStageConfig('INTAKE', c as unknown as Record<string, unknown>)
|
||||
}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
@ -323,6 +324,7 @@ export default function EditPipelinePage() {
|
|||
<MainTrackSection
|
||||
stages={mainTrack?.stages ?? []}
|
||||
onChange={updateMainTrackStages}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
@ -339,6 +341,7 @@ export default function EditPipelinePage() {
|
|||
onChange={(c) =>
|
||||
updateStageConfig('FILTER', c as unknown as Record<string, unknown>)
|
||||
}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
@ -355,6 +358,7 @@ export default function EditPipelinePage() {
|
|||
onChange={(c) =>
|
||||
updateStageConfig('EVALUATION', c as unknown as Record<string, unknown>)
|
||||
}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
@ -369,6 +373,7 @@ export default function EditPipelinePage() {
|
|||
<AwardsSection
|
||||
tracks={state.tracks}
|
||||
onChange={(tracks) => updateState({ tracks })}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
@ -385,6 +390,7 @@ export default function EditPipelinePage() {
|
|||
onChange={(c) =>
|
||||
updateStageConfig('LIVE_FINAL', c as unknown as Record<string, unknown>)
|
||||
}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
@ -403,6 +409,7 @@ export default function EditPipelinePage() {
|
|||
onOverridePolicyChange={(overridePolicy) =>
|
||||
updateState({ overridePolicy })
|
||||
}
|
||||
isActive={isActive}
|
||||
/>
|
||||
</WizardSection>
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,12 @@ export default function PipelineListPage() {
|
|||
{pipelines && pipelines.length > 0 && (
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{pipelines.map((pipeline) => (
|
||||
<Card key={pipeline.id} className="group hover:shadow-md transition-shadow">
|
||||
<Link
|
||||
key={pipeline.id}
|
||||
href={`/admin/rounds/pipeline/${pipeline.id}` as Route}
|
||||
className="block"
|
||||
>
|
||||
<Card className="group hover:shadow-md transition-shadow cursor-pointer">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="min-w-0 flex-1">
|
||||
|
|
@ -160,6 +165,7 @@ export default function PipelineListPage() {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
|
|
@ -198,6 +204,7 @@ export default function PipelineListPage() {
|
|||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ export default function ApplicantPipelinePage() {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href={"/applicant/messages" as Route}
|
||||
href={"/applicant/mentor" as Route}
|
||||
className="group flex items-center gap-3 rounded-xl border p-4 transition-all hover:border-amber-500/30 hover:bg-amber-50/50 hover:-translate-y-0.5 hover:shadow-md"
|
||||
>
|
||||
<div className="rounded-lg bg-amber-50 p-2 dark:bg-amber-950/40">
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export default function MentorDashboard() {
|
|||
{/* Quick Actions */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<Link href={'/mentor/messages' as Route}>
|
||||
<Link href={'/mentor/projects' as Route}>
|
||||
<Mail className="mr-2 h-4 w-4" />
|
||||
Messages
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ import type { EvaluationConfig } from '@/types/pipeline-wizard'
|
|||
type AssignmentSectionProps = {
|
||||
config: EvaluationConfig
|
||||
onChange: (config: EvaluationConfig) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export function AssignmentSection({ config, onChange }: AssignmentSectionProps) {
|
||||
export function AssignmentSection({ config, onChange, isActive }: AssignmentSectionProps) {
|
||||
const updateConfig = (updates: Partial<EvaluationConfig>) => {
|
||||
onChange({ ...config, ...updates })
|
||||
}
|
||||
|
|
@ -32,6 +33,7 @@ export function AssignmentSection({ config, onChange }: AssignmentSectionProps)
|
|||
min={1}
|
||||
max={20}
|
||||
value={config.requiredReviews}
|
||||
disabled={isActive}
|
||||
onChange={(e) =>
|
||||
updateConfig({ requiredReviews: parseInt(e.target.value) || 3 })
|
||||
}
|
||||
|
|
@ -48,6 +50,7 @@ export function AssignmentSection({ config, onChange }: AssignmentSectionProps)
|
|||
min={1}
|
||||
max={100}
|
||||
value={config.maxLoadPerJuror}
|
||||
disabled={isActive}
|
||||
onChange={(e) =>
|
||||
updateConfig({ maxLoadPerJuror: parseInt(e.target.value) || 20 })
|
||||
}
|
||||
|
|
@ -64,6 +67,7 @@ export function AssignmentSection({ config, onChange }: AssignmentSectionProps)
|
|||
min={0}
|
||||
max={50}
|
||||
value={config.minLoadPerJuror}
|
||||
disabled={isActive}
|
||||
onChange={(e) =>
|
||||
updateConfig({ minLoadPerJuror: parseInt(e.target.value) || 5 })
|
||||
}
|
||||
|
|
@ -74,6 +78,12 @@ export function AssignmentSection({ config, onChange }: AssignmentSectionProps)
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{config.minLoadPerJuror > config.maxLoadPerJuror && (
|
||||
<p className="text-sm text-destructive">
|
||||
Min load per juror cannot exceed max load per juror.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>Availability Weighting</Label>
|
||||
|
|
@ -86,6 +96,7 @@ export function AssignmentSection({ config, onChange }: AssignmentSectionProps)
|
|||
onCheckedChange={(checked) =>
|
||||
updateConfig({ availabilityWeighting: checked })
|
||||
}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -98,6 +109,7 @@ export function AssignmentSection({ config, onChange }: AssignmentSectionProps)
|
|||
overflowPolicy: value as EvaluationConfig['overflowPolicy'],
|
||||
})
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import type { RoutingMode, DecisionMode, AwardScoringMode } from '@prisma/client
|
|||
type AwardsSectionProps = {
|
||||
tracks: WizardTrackConfig[]
|
||||
onChange: (tracks: WizardTrackConfig[]) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
function slugify(name: string): string {
|
||||
|
|
@ -40,7 +41,7 @@ function slugify(name: string): string {
|
|||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
|
||||
export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
||||
export function AwardsSection({ tracks, onChange, isActive }: AwardsSectionProps) {
|
||||
const awardTracks = tracks.filter((t) => t.kind === 'AWARD')
|
||||
const nonAwardTracks = tracks.filter((t) => t.kind !== 'AWARD')
|
||||
|
||||
|
|
@ -72,7 +73,7 @@ export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
|||
<p className="text-sm text-muted-foreground">
|
||||
Configure special award tracks that run alongside the main competition.
|
||||
</p>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addAward}>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addAward} disabled={isActive}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
Add Award Track
|
||||
</Button>
|
||||
|
|
@ -100,6 +101,7 @@ export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
||||
disabled={isActive}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
|
|
@ -129,6 +131,7 @@ export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
|||
<Input
|
||||
placeholder="e.g., Innovation Award"
|
||||
value={track.awardConfig?.name ?? track.name}
|
||||
disabled={isActive}
|
||||
onChange={(e) => {
|
||||
const name = e.target.value
|
||||
updateAward(index, {
|
||||
|
|
@ -151,6 +154,7 @@ export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
|||
routingModeDefault: value as RoutingMode,
|
||||
})
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue />
|
||||
|
|
@ -178,6 +182,7 @@ export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
|||
onValueChange={(value) =>
|
||||
updateAward(index, { decisionMode: value as DecisionMode })
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue />
|
||||
|
|
@ -203,6 +208,7 @@ export function AwardsSection({ tracks, onChange }: AwardsSectionProps) {
|
|||
},
|
||||
})
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger className="text-sm">
|
||||
<SelectValue />
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@ import type { FilterConfig, FilterRuleConfig } from '@/types/pipeline-wizard'
|
|||
type FilteringSectionProps = {
|
||||
config: FilterConfig
|
||||
onChange: (config: FilterConfig) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
||||
export function FilteringSection({ config, onChange, isActive }: FilteringSectionProps) {
|
||||
const updateConfig = (updates: Partial<FilterConfig>) => {
|
||||
onChange({ ...config, ...updates })
|
||||
}
|
||||
|
|
@ -57,7 +58,7 @@ export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
|||
Deterministic rules that projects must pass. Applied in order.
|
||||
</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addRule}>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addRule} disabled={isActive}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
Add Rule
|
||||
</Button>
|
||||
|
|
@ -72,11 +73,13 @@ export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
|||
placeholder="Field name"
|
||||
value={rule.field}
|
||||
className="h-8 text-sm"
|
||||
disabled={isActive}
|
||||
onChange={(e) => updateRule(index, { field: e.target.value })}
|
||||
/>
|
||||
<Select
|
||||
value={rule.operator}
|
||||
onValueChange={(value) => updateRule(index, { operator: value })}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-sm">
|
||||
<SelectValue />
|
||||
|
|
@ -94,6 +97,7 @@ export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
|||
placeholder="Value"
|
||||
value={String(rule.value)}
|
||||
className="h-8 text-sm"
|
||||
disabled={isActive}
|
||||
onChange={(e) => updateRule(index, { value: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -103,6 +107,7 @@ export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
|||
size="icon"
|
||||
className="shrink-0 h-7 w-7 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => removeRule(index)}
|
||||
disabled={isActive}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
|
|
@ -130,6 +135,7 @@ export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
|||
<Switch
|
||||
checked={config.aiRubricEnabled}
|
||||
onCheckedChange={(checked) => updateConfig({ aiRubricEnabled: checked })}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -196,6 +202,7 @@ export function FilteringSection({ config, onChange }: FilteringSectionProps) {
|
|||
<Switch
|
||||
checked={config.manualQueueEnabled}
|
||||
onCheckedChange={(checked) => updateConfig({ manualQueueEnabled: checked })}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ import type { IntakeConfig, FileRequirementConfig } from '@/types/pipeline-wizar
|
|||
type IntakeSectionProps = {
|
||||
config: IntakeConfig
|
||||
onChange: (config: IntakeConfig) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export function IntakeSection({ config, onChange }: IntakeSectionProps) {
|
||||
export function IntakeSection({ config, onChange, isActive }: IntakeSectionProps) {
|
||||
const updateConfig = (updates: Partial<IntakeConfig>) => {
|
||||
onChange({ ...config, ...updates })
|
||||
}
|
||||
|
|
@ -54,6 +55,12 @@ export function IntakeSection({ config, onChange }: IntakeSectionProps) {
|
|||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{isActive && (
|
||||
<p className="text-sm text-amber-600 bg-amber-50 rounded-md px-3 py-2">
|
||||
Some settings are locked because this pipeline is active.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Submission Window */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -68,6 +75,7 @@ export function IntakeSection({ config, onChange }: IntakeSectionProps) {
|
|||
onCheckedChange={(checked) =>
|
||||
updateConfig({ submissionWindowEnabled: checked })
|
||||
}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -83,6 +91,7 @@ export function IntakeSection({ config, onChange }: IntakeSectionProps) {
|
|||
lateSubmissionPolicy: value as IntakeConfig['lateSubmissionPolicy'],
|
||||
})
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
|
|
@ -115,7 +124,7 @@ export function IntakeSection({ config, onChange }: IntakeSectionProps) {
|
|||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>File Requirements</Label>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addFileReq}>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addFileReq} disabled={isActive}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
Add Requirement
|
||||
</Button>
|
||||
|
|
@ -183,6 +192,7 @@ export function IntakeSection({ config, onChange }: IntakeSectionProps) {
|
|||
size="icon"
|
||||
className="shrink-0 text-muted-foreground hover:text-destructive"
|
||||
onClick={() => removeFileReq(index)}
|
||||
disabled={isActive}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ import type { LiveFinalConfig } from '@/types/pipeline-wizard'
|
|||
type LiveFinalsSectionProps = {
|
||||
config: LiveFinalConfig
|
||||
onChange: (config: LiveFinalConfig) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export function LiveFinalsSection({ config, onChange }: LiveFinalsSectionProps) {
|
||||
export function LiveFinalsSection({ config, onChange, isActive }: LiveFinalsSectionProps) {
|
||||
const updateConfig = (updates: Partial<LiveFinalConfig>) => {
|
||||
onChange({ ...config, ...updates })
|
||||
}
|
||||
|
|
@ -36,6 +37,7 @@ export function LiveFinalsSection({ config, onChange }: LiveFinalsSectionProps)
|
|||
onCheckedChange={(checked) =>
|
||||
updateConfig({ juryVotingEnabled: checked })
|
||||
}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -52,6 +54,7 @@ export function LiveFinalsSection({ config, onChange }: LiveFinalsSectionProps)
|
|||
onCheckedChange={(checked) =>
|
||||
updateConfig({ audienceVotingEnabled: checked })
|
||||
}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -91,6 +94,7 @@ export function LiveFinalsSection({ config, onChange }: LiveFinalsSectionProps)
|
|||
cohortSetupMode: value as LiveFinalConfig['cohortSetupMode'],
|
||||
})
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
|
|
@ -115,6 +119,7 @@ export function LiveFinalsSection({ config, onChange }: LiveFinalsSectionProps)
|
|||
revealPolicy: value as LiveFinalConfig['revealPolicy'],
|
||||
})
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import type { StageType } from '@prisma/client'
|
|||
type MainTrackSectionProps = {
|
||||
stages: WizardStageConfig[]
|
||||
onChange: (stages: WizardStageConfig[]) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
const STAGE_TYPE_OPTIONS: { value: StageType; label: string; color: string }[] = [
|
||||
|
|
@ -45,7 +46,7 @@ function slugify(name: string): string {
|
|||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
|
||||
export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
||||
export function MainTrackSection({ stages, onChange, isActive }: MainTrackSectionProps) {
|
||||
const updateStage = useCallback(
|
||||
(index: number, updates: Partial<WizardStageConfig>) => {
|
||||
const updated = [...stages]
|
||||
|
|
@ -95,15 +96,22 @@ export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
|||
Drag to reorder. Minimum 2 stages required.
|
||||
</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addStage}>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addStage} disabled={isActive}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
Add Stage
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{isActive && (
|
||||
<p className="text-sm text-amber-600 bg-amber-50 rounded-md px-3 py-2">
|
||||
Stage structure is locked because this pipeline is active. Use the Advanced editor for config changes.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
{stages.map((stage, index) => {
|
||||
const typeInfo = STAGE_TYPE_OPTIONS.find((t) => t.value === stage.stageType)
|
||||
const hasDuplicateSlug = stage.slug && stages.some((s, i) => i !== index && s.slug === stage.slug)
|
||||
return (
|
||||
<Card key={index}>
|
||||
<CardContent className="py-3 px-4">
|
||||
|
|
@ -115,7 +123,7 @@ export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5"
|
||||
disabled={index === 0}
|
||||
disabled={isActive || index === 0}
|
||||
onClick={() => moveStage(index, 'up')}
|
||||
>
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
|
|
@ -126,7 +134,7 @@ export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-5 w-5"
|
||||
disabled={index === stages.length - 1}
|
||||
disabled={isActive || index === stages.length - 1}
|
||||
onClick={() => moveStage(index, 'down')}
|
||||
>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
|
|
@ -143,12 +151,16 @@ export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
|||
<Input
|
||||
placeholder="Stage name"
|
||||
value={stage.name}
|
||||
className="h-8 text-sm"
|
||||
className={cn('h-8 text-sm', hasDuplicateSlug && 'border-destructive')}
|
||||
disabled={isActive}
|
||||
onChange={(e) => {
|
||||
const name = e.target.value
|
||||
updateStage(index, { name, slug: slugify(name) })
|
||||
}}
|
||||
/>
|
||||
{hasDuplicateSlug && (
|
||||
<p className="text-[10px] text-destructive mt-0.5">Duplicate name</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stage type */}
|
||||
|
|
@ -158,6 +170,7 @@ export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
|||
onValueChange={(value) =>
|
||||
updateStage(index, { stageType: value as StageType })
|
||||
}
|
||||
disabled={isActive}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue />
|
||||
|
|
@ -186,7 +199,7 @@ export function MainTrackSection({ stages, onChange }: MainTrackSectionProps) {
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 h-7 w-7 text-muted-foreground hover:text-destructive"
|
||||
disabled={stages.length <= 2}
|
||||
disabled={isActive || stages.length <= 2}
|
||||
onClick={() => removeStage(index)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ type NotificationsSectionProps = {
|
|||
onChange: (config: Record<string, boolean>) => void
|
||||
overridePolicy: Record<string, unknown>
|
||||
onOverridePolicyChange: (policy: Record<string, unknown>) => void
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
const NOTIFICATION_EVENTS = [
|
||||
|
|
@ -60,6 +61,7 @@ export function NotificationsSection({
|
|||
onChange,
|
||||
overridePolicy,
|
||||
onOverridePolicyChange,
|
||||
isActive,
|
||||
}: NotificationsSectionProps) {
|
||||
const toggleEvent = (key: string, enabled: boolean) => {
|
||||
onChange({ ...config, [key]: enabled })
|
||||
|
|
@ -88,6 +90,7 @@ export function NotificationsSection({
|
|||
<Switch
|
||||
checked={config[event.key] !== false}
|
||||
onCheckedChange={(checked) => toggleEvent(event.key, checked)}
|
||||
disabled={isActive}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
Loading…
Reference in New Issue