Skip to content

Commit

Permalink
Merge pull request #48 from openimis/feature/CM-880
Browse files Browse the repository at this point in the history
CM-880: standalone submenu items under 'Task' which will redirect to the page with one searcher for all tasks
  • Loading branch information
jdolkowski authored Apr 24, 2024
2 parents b7040f3 + 3a4f124 commit 4966854
Show file tree
Hide file tree
Showing 9 changed files with 443 additions and 2 deletions.
146 changes: 146 additions & 0 deletions src/components/TaskAllFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React from 'react';
import { injectIntl } from 'react-intl';
import { Grid } from '@material-ui/core';
import { withTheme, withStyles } from '@material-ui/core/styles';
import _debounce from 'lodash/debounce';
import {
TextInput, PublishedComponent, formatMessage, decodeId, toISODateTime,
} from '@openimis/fe-core';
import { defaultFilterStyles } from '../utils/styles';
import {
CONTAINS_LOOKUP, DEFAULT_DEBOUNCE_TIME, EMPTY_STRING, MODULE_NAME,
} from '../constants';

function TaskAllFilter({
intl, classes, filters, onChangeFilters,
}) {
const debouncedOnChangeFilters = _debounce(onChangeFilters, DEFAULT_DEBOUNCE_TIME);

const filterValue = (filterName) => filters?.[filterName]?.value;

const filterTextFieldValue = (filterName) => filters?.[filterName]?.value ?? EMPTY_STRING;

const onChangeStringFilter = (filterName, lookup = null) => (value) => {
if (lookup) {
debouncedOnChangeFilters([
{
id: filterName,
value,
filter: `${filterName}_${lookup}: "${value}"`,
},
]);
} else {
onChangeFilters([
{
id: filterName,
value,
filter: `${filterName}: "${value}"`,
},
]);
}
};

return (
<Grid container className={classes.form}>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="tasksManagement.taskSourcesPicker"
module={MODULE_NAME}
withLabel
nullLabel={formatMessage(intl, MODULE_NAME, 'any')}
withNull
value={filterValue('source')}
onChange={(value) => onChangeFilters([
{
id: 'source',
value,
filter: value ? `source: "${value}"` : EMPTY_STRING,
},
])}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="tasksManagement.taskTypesPicker"
module={MODULE_NAME}
withLabel
nullLabel={formatMessage(intl, MODULE_NAME, 'any')}
withNull
value={filterValue('businessEvent')}
onChange={onChangeStringFilter('businessEvent', CONTAINS_LOOKUP)}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<TextInput
module={MODULE_NAME}
label="task.entity"
value={filterTextFieldValue('entityString')}
onChange={onChangeStringFilter('entityString', CONTAINS_LOOKUP)}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="tasksManagement.taskGroupPicker"
module={MODULE_NAME}
value={filterValue('taskGroupId')}
onChange={(value) => onChangeFilters([
{
id: 'taskGroupId',
value,
filter: value?.id ? `taskGroupId: "${decodeId(value.id)}"` : '',
},
])}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="tasksManagement.taskStatusPicker"
module={MODULE_NAME}
withLabel
nullLabel={formatMessage(intl, MODULE_NAME, 'any')}
withNull
value={filterValue('status')}
onChange={(value) => onChangeFilters([
{
id: 'status',
value,
filter: value ? `status: ${value}` : EMPTY_STRING,
},
])}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module={MODULE_NAME}
label={formatMessage(intl, MODULE_NAME, 'task.dateCreated.after')}
value={filterValue('dateCreated_Gte')}
onChange={(v) => onChangeFilters([
{
id: 'dateCreated_Gte',
value: v,
filter: `dateCreated_Gte: "${toISODateTime(v)}"`,
},
])}
/>
</Grid>
<Grid item xs={3} className={classes.item}>
<PublishedComponent
pubRef="core.DatePicker"
module={MODULE_NAME}
label={formatMessage(intl, MODULE_NAME, 'task.dateCreated.before')}
value={filterValue('dateCreated_Lte')}
onChange={(v) => onChangeFilters([
{
id: 'dateCreated_Lte',
value: v,
filter: `dateCreated_Lte: "${toISODateTime(v)}"`,
},
])}
/>
</Grid>
</Grid>
);
}

export default injectIntl(withTheme(withStyles(defaultFilterStyles)(TaskAllFilter)));
142 changes: 142 additions & 0 deletions src/components/TaskAllSearcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Searcher,
useHistory,
historyPush,
useModulesManager,
useTranslations,
} from '@openimis/fe-core';
import { IconButton, Tooltip } from '@material-ui/core';
import VisibilityIcon from '@material-ui/icons/Visibility';
import {
RIGHT_TASKS_MANAGEMENT_SEARCH, DEFAULT_PAGE_SIZE, ROWS_PER_PAGE_OPTIONS, TASK_STATUS, TASK_ROUTE,
} from '../constants';
import TaskAllFilter from './TaskAllFilter';
import { fetchTasks } from '../actions';
import trimBusinessEvent from '../utils/trimBusinessEvent';

function TaskAllSearcher({
rights, showFilters = true,
}) {
const history = useHistory();
const modulesManager = useModulesManager();
const dispatch = useDispatch();
const {
formatMessage,
formatMessageWithValues,
formatDateTimeFromISO,
} = useTranslations('tasksManagement', modulesManager);

const fetchingTasks = useSelector((state) => state?.tasksManagement?.fetchingTasks);
const fetchedTasks = useSelector((state) => state?.tasksManagement?.fetchedTasks);
const errorTasks = useSelector((state) => state?.tasksManagement?.errorTasks);
const tasks = useSelector((state) => state?.tasksManagement?.tasks);
const tasksPageInfo = useSelector((state) => state?.tasksManagement?.tasksPageInfo);
const tasksTotalCount = useSelector((state) => state?.tasksManagement?.tasksTotalCount);

const openTask = (task, newTab = false) => historyPush(
modulesManager,
history,
TASK_ROUTE,
[task?.id],
newTab,
);

const onDoubleClick = (task) => openTask(task);
const fetch = (params) => dispatch(fetchTasks(modulesManager, params));

const rowIdentifier = (task) => task.id;

const isRowDisabled = (_, task) => task.status !== TASK_STATUS.ACCEPTED;

const headers = () => {
const headers = [
'task.source',
'task.type',
'task.entity',
'task.assignee',
'task.dateCreated',
'task.status',
];
if (rights.includes(RIGHT_TASKS_MANAGEMENT_SEARCH)) {
headers.push('emptyLabel');
}
return headers;
};

const sorts = () => [
['source', true],
['type', true],
['entity', true],
['assignee', true],
['date_created', true],
['status', true],
];

const itemFormatters = () => [
(task) => task.source,
(task) => trimBusinessEvent(task.businessEvent),
(task) => task.entityString,
(task) => task?.taskGroup?.code,
(task) => formatDateTimeFromISO(task?.dateCreated),
(task) => task.status,
(task) => (
<Tooltip title={formatMessage('viewDetailsButton.tooltip')}>
<IconButton
onClick={() => openTask(task)}
>
<VisibilityIcon />
</IconButton>
</Tooltip>
),
];

const defaultFilters = () => {
const filters = {
isDeleted: {
value: false,
filter: 'isDeleted: false',
},
};
return filters;
};

const taskFilter = (props) => (
<TaskAllFilter
intl={props.intl}
classes={props.classes}
filters={props.filters}
onChangeFilters={props.onChangeFilters}
/>
);

return (
<Searcher
module="tasksManagement"
FilterPane={showFilters && taskFilter}
fetch={fetch}
items={tasks}
itemsPageInfo={tasksPageInfo}
fetchingItems={fetchingTasks}
fetchedItems={fetchedTasks}
errorItems={errorTasks}
tableTitle={formatMessageWithValues('task.searcherResultsTitle', {
tasksTotalCount,
})}
headers={headers}
itemFormatters={itemFormatters}
sorts={sorts}
rowsPerPageOptions={ROWS_PER_PAGE_OPTIONS}
defaultPageSize={DEFAULT_PAGE_SIZE}
defaultOrderBy="-dateCreated"
rowIdentifier={rowIdentifier}
onDoubleClick={onDoubleClick}
defaultFilters={defaultFilters()}
rowDisabled={isRowDisabled}
rights={rights}
/>
);
}

export default TaskAllSearcher;
27 changes: 27 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const TASK_STATUS_LIST = [
export const GROUP_RESOLVE_POLICY_LIST = [GROUP_RESOLVE_POLICY.ALL, GROUP_RESOLVE_POLICY.ANY, GROUP_RESOLVE_POLICY.N];

export const RIGHT_TASKS_MANAGEMENT_SEARCH = 191001;
export const RIGHT_TASKS_MANAGEMENT_SEARCH_ALL = 191005;

export const TASKS_MANAGEMENT_ROUTE_GROUPS_GROUP = 'tasksManagement.route.group';

Expand All @@ -65,3 +66,29 @@ export const FAILED = 'FAILED';
export const DOT = '.';

export const TASK_ROUTE = 'tasksManagement.route.task';

export const TASK_AVAILABLE_SOURCES = [
'IndividualService',
'GroupIndividualService',
'CreateGroupAndMoveIndividualService',
'BenefitPlanService',
'BeneficiaryService',
'calcrule_social_protection',
'import_valid_items',
'payroll',
'payroll_reconciliation',
'payroll_reject',
'payroll_delete',
'CreateDeduplicationReviewTasksService',
];

export const TASK_AVAILABLE_TYPES = [
'create',
'update',
'calculate',
'import_valid_items',
'accept_payroll',
'payroll_reconciliation',
'payroll_reject',
'payroll_delete',
];
7 changes: 7 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ import TaskSearcher from './components/TaskSearcher';
import getAdminMainMenuContributions from './contributions/AdminMainMenuContributions';
import { TASK_ROUTE } from './constants';
import { fetchTask, resolveTask } from './actions';
import TasksAllPage from './pages/TasksAllPage';
import TaskTypesPicker from './pickers/TaskTypesPicker';
import TaskSourcesPicker from './pickers/TaskSourcesPicker';

const ROUTE_TASKS_MANAGEMENT = 'tasks';
const ROUTE_TASK_MANAGEMENT = 'tasks/task';
const ROUTE_TASKS_ALL_MANAGEMENT = 'allTasks';

const ROUTE_GROUPS_MANAGEMENT = 'tasks/groups';
const ROUTE_GROUP_MANAGEMENT = 'tasks/groups/group';
Expand All @@ -30,6 +34,7 @@ const DEFAULT_CONFIG = {
'admin.MainMenu': [...getAdminMainMenuContributions()],
'core.Router': [
{ path: ROUTE_TASKS_MANAGEMENT, component: TasksManagementPage },
{ path: ROUTE_TASKS_ALL_MANAGEMENT, component: TasksAllPage },
{ path: `${ROUTE_TASK_MANAGEMENT}/:task_uuid?`, component: TaskDetailsPage },
{ path: ROUTE_GROUPS_MANAGEMENT, component: GroupsManagementPage },
{ path: `${ROUTE_GROUP_MANAGEMENT}/:task_group_uuid?`, component: TaskGroupPage },
Expand All @@ -38,6 +43,8 @@ const DEFAULT_CONFIG = {
{ key: TASK_ROUTE, ref: ROUTE_TASK_MANAGEMENT },
{ key: 'tasksManagement.route.group', ref: ROUTE_GROUP_MANAGEMENT },
{ key: 'tasksManagement.taskStatusPicker', ref: TaskStatusPicker },
{ key: 'tasksManagement.taskTypesPicker', ref: TaskTypesPicker },
{ key: 'tasksManagement.taskSourcesPicker', ref: TaskSourcesPicker },
{ key: 'tasksManagement.taskPreviewCell', ref: TaskPreviewCell },
{ key: 'tasksManagement.taskGroupPicker', ref: TaskGroupPicker },
{ key: 'tasksManagement.taskSearcher', ref: TaskSearcher },
Expand Down
11 changes: 10 additions & 1 deletion src/menus/TasksMainMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { useSelector } from 'react-redux';
import { injectIntl } from 'react-intl';
import AssignmentIcon from '@material-ui/icons/Assignment';
import { formatMessage, MainMenuContribution, withModulesManager } from '@openimis/fe-core';
import { TASKS_MANAGEMENT_MAIN_MENU_CONTRIBUTION_KEY } from '../constants';
import {
RIGHT_TASKS_MANAGEMENT_SEARCH_ALL,
TASKS_MANAGEMENT_MAIN_MENU_CONTRIBUTION_KEY,
} from '../constants';

function TasksMainMenu(props) {
const rights = useSelector((store) => store.core?.user?.i_user?.rights ?? []);
Expand All @@ -17,6 +20,12 @@ function TasksMainMenu(props) {
icon: <AssignmentIcon />,
route: '/tasks',
},
{
text: formatMessage(props.intl, 'tasksManagement', 'entries.tasksManagementAllView'),
icon: <AssignmentIcon />,
route: '/AllTasks',
filter: (rights) => rights.includes(RIGHT_TASKS_MANAGEMENT_SEARCH_ALL),
},
];
entries.push(
...props.modulesManager
Expand Down
Loading

0 comments on commit 4966854

Please sign in to comment.