Skip to content
Open
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 @@ -2,7 +2,6 @@ import { FC } from 'react';
import { defineMessages } from 'react-intl';
import { Card, CardContent, ListSubheader } from '@mui/material';

import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon';
import { selectDuplicationStore } from 'course/duplication/selectors';
Expand All @@ -12,6 +11,7 @@ import {
DuplicationTabData,
} from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { FC } from 'react';
import { defineMessages } from 'react-intl';
import { Card, CardContent, ListSubheader } from '@mui/material';

import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import { selectDuplicationStore } from 'course/duplication/selectors';
import {
DuplicationFolderData,
DuplicationMaterialData,
} from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { FC } from 'react';
import { defineMessages } from 'react-intl';
import { Card, CardContent, ListSubheader } from '@mui/material';

import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon';
import { selectDuplicationStore } from 'course/duplication/selectors';
Expand All @@ -11,6 +10,7 @@ import {
DuplicationVideoTabData,
} from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { defineMessages } from 'react-intl';
import { ListSubheader, Typography } from '@mui/material';

import BulkSelectors from 'course/duplication/components/BulkSelectors';
import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon';
import { selectDuplicationStore } from 'course/duplication/selectors';
import { actions } from 'course/duplication/store';
import { DuplicationAchievementData } from 'course/duplication/types';
import { getAchievementBadgeUrl } from 'course/helper/achievements';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import Thumbnail from 'lib/components/core/Thumbnail';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { defineMessages } from 'react-intl';
import { ListSubheader, Typography } from '@mui/material';

import BulkSelectors from 'course/duplication/components/BulkSelectors';
import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon';
import {
Expand All @@ -17,6 +16,7 @@ import {
DuplicationTabData,
} from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { defineMessages } from 'react-intl';
import { ListSubheader, Typography } from '@mui/material';

import BulkSelectors from 'course/duplication/components/BulkSelectors';
import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import { selectDuplicationStore } from 'course/duplication/selectors';
import { actions } from 'course/duplication/store';
Expand All @@ -12,6 +11,7 @@ import {
DuplicationMaterialData,
} from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { defineMessages } from 'react-intl';
import { ListSubheader, Typography } from '@mui/material';

import BulkSelectors from 'course/duplication/components/BulkSelectors';
import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon';
import { selectDuplicationStore } from 'course/duplication/selectors';
import { actions } from 'course/duplication/store';
import { DuplicationSurveyData } from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { defineMessages } from 'react-intl';
import { ListSubheader, Typography } from '@mui/material';

import BulkSelectors from 'course/duplication/components/BulkSelectors';
import IndentedCheckbox from 'course/duplication/components/IndentedCheckbox';
import TypeBadge from 'course/duplication/components/TypeBadge';
import UnpublishedIcon from 'course/duplication/components/UnpublishedIcon';
import { selectDuplicationStore } from 'course/duplication/selectors';
Expand All @@ -13,6 +12,7 @@ import {
DuplicationVideoTabData,
} from 'course/duplication/types';
import componentTranslations from 'course/translations';
import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { FC, ReactNode } from 'react';

import IndentedCheckbox from 'lib/components/core/IndentedCheckbox';

import { ColumnPickerRenderCtx } from '../builder';

interface ColumnPickerTreeGroupProps {
label: string;
/** All leaf column ids that belong to this group (used to derive parent state). */
childIds: string[];
ctx: ColumnPickerRenderCtx;
/** Ids that are locked visible — parent checkbox is disabled when all children are locked. */
locked?: string[];
indentLevel?: number;
children: ReactNode;
}

/**
* Renders a parent checkbox whose checked/indeterminate state mirrors its children's
* visibility, and whose onChange bulk-toggles all children via ctx.setManyVisible.
* Children are rendered below (not inline), giving a vertical tree layout.
*/
const ColumnPickerTreeGroup: FC<ColumnPickerTreeGroupProps> = ({
label,
childIds,
ctx,
locked = [],
indentLevel = 0,
children,
}) => {
const visibleCount = childIds.filter((id) => ctx.isVisible(id)).length;
const allVisible = childIds.length > 0 && visibleCount === childIds.length;
const someVisible = visibleCount > 0 && !allVisible;
const allLocked =
childIds.length > 0 && childIds.every((id) => locked.includes(id));

return (
<div>
<IndentedCheckbox
checked={allVisible}
disabled={allLocked}
indentLevel={indentLevel}
indeterminate={someVisible}
label={label}
onChange={(e) => ctx.setManyVisible(childIds, e.target.checked)}
/>
<div>{children}</div>
</div>
);
};

export default ColumnPickerTreeGroup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { useEffect, useState } from 'react';
import { defineMessages } from 'react-intl';
import {
Alert,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
} from '@mui/material';

import useTranslation from 'lib/hooks/useTranslation';

import { ColumnPickerTemplate } from '../builder';

const translations = defineMessages({
defaultTitle: {
id: 'lib.components.table.MuiColumnPickerDialog.defaultTitle',
defaultMessage: 'Select columns',
},
apply: {
id: 'lib.components.table.MuiColumnPickerDialog.apply',
defaultMessage: 'Apply to view',
},
cancel: {
id: 'lib.components.table.MuiColumnPickerDialog.cancel',
defaultMessage: 'Cancel',
},
defaultExport: {
id: 'lib.components.table.MuiColumnPickerDialog.export',
defaultMessage: 'Apply and Export',
},
});

interface MuiColumnPickerDialogProps {
open: boolean;
onClose: () => void;
initialVisibility: Record<string, boolean>;
locked?: string[];
columnPicker: ColumnPickerTemplate;
commitColumnVisibility: (next: Record<string, boolean>) => void;
onExportFromPicker?: (visibility: Record<string, boolean>) => void;
}

const enforceLockedLocal = (
next: Record<string, boolean>,
locked: string[] | undefined,
): Record<string, boolean> => {
if (!locked || locked.length === 0) return next;
const enforced = { ...next };
locked.forEach((id) => {
enforced[id] = true;
});
return enforced;
};

const MuiColumnPickerDialog = ({
open,
onClose,
initialVisibility,
locked,
columnPicker,
commitColumnVisibility,
onExportFromPicker,
}: MuiColumnPickerDialogProps): JSX.Element => {
const { t } = useTranslation();
const [staged, setStaged] = useState<Record<string, boolean>>(() =>
enforceLockedLocal({ ...initialVisibility }, locked),
);

const dataColumnIds = columnPicker.dataColumnIds;
const hasDataColumns =
!dataColumnIds ||
dataColumnIds.length === 0 ||
dataColumnIds.some((id) => staged[id]);

useEffect(() => {
if (open) {
setStaged(enforceLockedLocal({ ...initialVisibility }, locked));
}
}, [open, initialVisibility, locked]);

const ctx = {
isVisible: (id: string): boolean => staged[id] ?? false,
setVisible: (id: string, v: boolean): void => {
if (locked?.includes(id)) return;
setStaged((prev) =>
Object.hasOwn(prev, id) ? { ...prev, [id]: v } : prev,
);
},
setManyVisible: (ids: string[], v: boolean): void => {
setStaged((prev) => {
const next = { ...prev };
let changed = false;
ids.forEach((id) => {
if (!Object.hasOwn(next, id)) return;
if (locked?.includes(id)) return;
if (next[id] !== v) {
next[id] = v;
changed = true;
}
});
return changed ? next : prev;
});
},
};

const commitAndClose = (): void => {
commitColumnVisibility(enforceLockedLocal(staged, locked));
onClose();
};

const cancelAndClose = (): void => {
onClose();
};

const exportAndClose = (): void => {
const enforced = enforceLockedLocal(staged, locked);
commitColumnVisibility(enforced);
onExportFromPicker?.(enforced);
onClose();
};

return (
<Dialog
aria-labelledby="column-picker-dialog-title"
fullWidth
maxWidth="sm"
onClose={cancelAndClose}
open={open}
>
<DialogTitle id="column-picker-dialog-title">
{columnPicker.dialogTitle ?? t(translations.defaultTitle)}
</DialogTitle>
<DialogContent dividers>{columnPicker.renderTree(ctx)}</DialogContent>
{!hasDataColumns && columnPicker.noDataColumnsHint && (
<Alert severity="info" sx={{ borderRadius: 0 }}>
{columnPicker.noDataColumnsHint}
</Alert>
)}
<DialogActions>
<Button onClick={cancelAndClose}>{t(translations.cancel)}</Button>
<Button onClick={commitAndClose}>{t(translations.apply)}</Button>
<Button color="primary" onClick={exportAndClose} variant="contained">
{columnPicker.exportLabel ?? t(translations.defaultExport)}
</Button>
</DialogActions>
</Dialog>
);
};

export default MuiColumnPickerDialog;
Loading