Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[Admin Docs](/)

***

# Variable: default

> `const` **default**: `React.FC`\<[`InterfaceApplyToSelectorProps`](../../../../../types/AdminPortal/ApplyToSelector/interface/interfaces/InterfaceApplyToSelectorProps.md)\>

Defined in: [src/components/AdminPortal/ApplyToSelector/ApplyToSelector.tsx:18](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/components/AdminPortal/ApplyToSelector/ApplyToSelector.tsx#L18)

A radio group selector for choosing action item scope.
Allows users to apply an action item to an entire series or a single instance.

## Param

Component props from InterfaceApplyToSelectorProps

## Returns

Radio group component for scope selection
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

> `const` **default**: `FC`\<[`IItemModalProps`](../../../../../types/shared-components/ActionItems/interface/interfaces/IItemModalProps.md)\>

Defined in: [src/screens/OrganizationActionItems/ActionItemModal/ActionItemModal.tsx:65](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/screens/OrganizationActionItems/ActionItemModal/ActionItemModal.tsx#L65)
Defined in: [src/screens/OrganizationActionItems/ActionItemModal/ActionItemModal.tsx:67](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/screens/OrganizationActionItems/ActionItemModal/ActionItemModal.tsx#L67)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[Admin Docs](/)

***

# Interface: InterfaceApplyToSelectorProps

Defined in: [src/types/AdminPortal/ApplyToSelector/interface.ts:11](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/types/AdminPortal/ApplyToSelector/interface.ts#L11)

Props for ApplyToSelector component.

## Properties

### applyTo

> **applyTo**: [`ApplyToType`](../type-aliases/ApplyToType.md)

Defined in: [src/types/AdminPortal/ApplyToSelector/interface.ts:13](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/types/AdminPortal/ApplyToSelector/interface.ts#L13)

Current selection value ('series' or 'instance')

***

### onChange()

> **onChange**: (`value`) => `void`

Defined in: [src/types/AdminPortal/ApplyToSelector/interface.ts:15](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/types/AdminPortal/ApplyToSelector/interface.ts#L15)

Callback fired when user changes the selection

#### Parameters

##### value

[`ApplyToType`](../type-aliases/ApplyToType.md)

#### Returns

`void`
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Admin Docs](/)

***

# Type Alias: ApplyToType

> **ApplyToType** = `"series"` \| `"instance"`

Defined in: [src/types/AdminPortal/ApplyToSelector/interface.ts:6](https://github.com/PalisadoesFoundation/talawa-admin/blob/main/src/types/AdminPortal/ApplyToSelector/interface.ts#L6)

Type representing the scope of action item application.
- 'series': Apply to entire recurring series
- 'instance': Apply to single event instance only
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { I18nextProvider } from 'react-i18next';
import i18n from 'utils/i18nForTest';
import ApplyToSelector from './ApplyToSelector';
import type { ApplyToType } from 'types/AdminPortal/ApplyToSelector/interface';

describe('ApplyToSelector', () => {
afterEach(() => {
vi.clearAllMocks();
});
const renderComponent = (
applyTo: ApplyToType = 'series',
onChange = vi.fn(),
) => {
return render(
<I18nextProvider i18n={i18n}>
<ApplyToSelector applyTo={applyTo} onChange={onChange} />
</I18nextProvider>,
);
};

it('renders both radio options', () => {
renderComponent();

expect(screen.getByLabelText(/entire series/i)).toBeInTheDocument();
expect(screen.getByLabelText(/this event only/i)).toBeInTheDocument();
});

it('shows series option checked when applyTo is series', () => {
renderComponent('series');

const seriesRadio = screen.getByLabelText(/entire series/i);
const instanceRadio = screen.getByLabelText(/this event only/i);

expect(seriesRadio).toBeChecked();
expect(instanceRadio).not.toBeChecked();
});

it('shows instance option checked when applyTo is instance', () => {
renderComponent('instance');

const seriesRadio = screen.getByLabelText(/entire series/i);
const instanceRadio = screen.getByLabelText(/this event only/i);

expect(seriesRadio).not.toBeChecked();
expect(instanceRadio).toBeChecked();
});

it('calls onChange with series when series radio is clicked', async () => {
const onChange = vi.fn();
renderComponent('instance', onChange);

const user = userEvent.setup();
await user.click(screen.getByLabelText(/entire series/i));

expect(onChange).toHaveBeenCalledWith('series');
});

it('calls onChange with instance when instance radio is clicked', async () => {
const onChange = vi.fn();
renderComponent('series', onChange);

const user = userEvent.setup();
await user.click(screen.getByLabelText(/this event only/i));

expect(onChange).toHaveBeenCalledWith('instance');
});
});
49 changes: 49 additions & 0 deletions src/components/AdminPortal/ApplyToSelector/ApplyToSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import { Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import type {
InterfaceApplyToSelectorProps,
ApplyToType,
} from 'types/AdminPortal/ApplyToSelector/interface';

export type { ApplyToType };

/**
* A radio group selector for choosing action item scope.
* Allows users to apply an action item to an entire series or a single instance.
*
* @param props - Component props from InterfaceApplyToSelectorProps
* @returns Radio group component for scope selection
*/
const ApplyToSelector: React.FC<InterfaceApplyToSelectorProps> = ({
applyTo,
onChange,
}) => {
const { t } = useTranslation('translation', {
keyPrefix: 'organizationActionItems',
});

return (
<Form.Group className="mb-3" as="fieldset">
<Form.Label as="legend">{t('applyTo')}</Form.Label>
<Form.Check
type="radio"
label={t('entireSeries')}
name="applyTo"
id="applyToSeries"
checked={applyTo === 'series'}
onChange={() => onChange('series')}
/>
<Form.Check
type="radio"
label={t('thisEventOnly')}
name="applyTo"
id="applyToInstance"
checked={applyTo === 'instance'}
onChange={() => onChange('instance')}
/>
</Form.Group>
);
Comment on lines +1 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.

⚠️ Potential issue | 🟠 Major

Avoid hard-coded radio name/id to prevent collisions across multiple instances.
Right now, rendering two ApplyToSelector components on the same page would (1) share a single radio group via name="applyTo" and (2) duplicate element ids.

Proposed fix (generate unique ids + group name)
-import React from 'react';
+import React, { useId } from 'react';
 import { Form } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 import type {
   InterfaceApplyToSelectorProps,
   ApplyToType,
 } from 'types/AdminPortal/ApplyToSelector/interface';

 export type { ApplyToType };

@@
 const ApplyToSelector: React.FC<InterfaceApplyToSelectorProps> = ({
   applyTo,
   onChange,
 }) => {
+  const uid = useId();
+  const name = `applyTo-${uid}`;
+  const seriesId = `${name}-series`;
+  const instanceId = `${name}-instance`;
   const { t } = useTranslation('translation', {
     keyPrefix: 'organizationActionItems',
   });

   return (
     <Form.Group className="mb-3" as="fieldset">
       <Form.Label as="legend">{t('applyTo')}</Form.Label>
       <Form.Check
         type="radio"
         label={t('entireSeries')}
-        name="applyTo"
-        id="applyToSeries"
+        name={name}
+        id={seriesId}
         checked={applyTo === 'series'}
         onChange={() => onChange('series')}
       />
       <Form.Check
         type="radio"
         label={t('thisEventOnly')}
-        name="applyTo"
-        id="applyToInstance"
+        name={name}
+        id={instanceId}
         checked={applyTo === 'instance'}
         onChange={() => onChange('instance')}
       />
     </Form.Group>
   );
 };
🤖 Prompt for AI Agents
In @src/components/AdminPortal/ApplyToSelector/ApplyToSelector.tsx around lines
1 - 46, ApplyToSelector currently hardcodes the radio group name and ids
("applyTo", "applyToSeries", "applyToInstance"), causing collisions when
multiple components render; change ApplyToSelector to generate a unique group
name and unique ids for the two Form.Check radios (e.g., via React.useId() or an
injected prop like idPrefix) and use that same unique name for both radio inputs
so they remain grouped per-instance while keeping checked/onChange logic
(applyTo prop and onChange handler) unchanged.

};

export default ApplyToSelector;
Loading
Loading