From 1d2d11a3530ce8c7c934773121ea021971f4811e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kruli=C5=A1?= Date: Sat, 7 Feb 2026 21:00:42 +0100 Subject: [PATCH 1/2] Implementing group archiving mode (and group selection) on Group Superadmin's page. --- src/locales/cs.json | 5 + src/locales/en.json | 5 + .../GroupsSuperadmin/GroupsSuperadmin.js | 397 +++++++++++++----- 3 files changed, 297 insertions(+), 110 deletions(-) diff --git a/src/locales/cs.json b/src/locales/cs.json index f7d694b..e19efc6 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -90,6 +90,10 @@ "app.groupsStudent.title": "Připojení ke skupinám jako student", "app.groupsSupervisor.addAttributeModal.existingAttributes": "Existující atributy", "app.groupsSupervisor.addAttributeModal.title": "Přidat atribut ke skupině", + "app.groupsSupervisor.archiveButton": "Archivovat vybrané skupiny", + "app.groupsSupervisor.archivingGroupsHeading": "Archivace starých semestrálních skupin", + "app.groupsSupervisor.archivingGroupsInfo": "Vybrané semestrální skupiny budou archivovány (včetně všech jejich podskupin). Tuto operaci lze v ReCodExu vrátit zpět individuálním vyjmutím skupin z archivu.", + "app.groupsSupervisor.cancelArchivingButton": "Zrušit archivaci", "app.groupsSupervisor.cancelPlantTermButton": "Zrušit osazování skupin", "app.groupsSupervisor.currentlyManagedGroups": "Skupiny", "app.groupsSupervisor.notSuperadmin": "Tato stránka je k dispozici pouze administrátorům ReCodExu.", @@ -101,6 +105,7 @@ "app.groupsSupervisor.plantTermGroupsSelectParents": "Prosíme, vybertete předměty, do kterých budou semestrální skupiny osazeny.", "app.groupsSupervisor.plantingFailed": "Osazování selhalo. Některé skupiny nemohly být vytvořeny. Jejich rodičovské skupiny jsou označeny níže.", "app.groupsSupervisor.plantingSucceeded": "Úspěšně {count, plural, one {byla osazena} =2 {byly osazeny} =3 {byly osazeny} =4 {byly osazeny} other {bylo osazeno}} {count} {count, plural, one {skupina} =2 {skupiny} =3 {skupiny} =4 {skupiny} other {skupin}}.", + "app.groupsSupervisor.startArchivingButton": "Vybrat skupiny pro archivaci", "app.groupsSupervisor.title": "Spravovat všechny skupiny a jejich vazby", "app.groupsTeacher.aboutStudentsInfo": "Studenti nejsou do skupiny přidáni automaticky, ale mohou se do skupiny připojit sami prostřednictvím rozšíření SIS-CodEx.", "app.groupsTeacher.bindGroupInfo": "Vybraný událost bude svázána s existující cílovou skupinou, kterou můžete vybrat níže.", diff --git a/src/locales/en.json b/src/locales/en.json index 4f5f977..e7415ca 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -90,6 +90,10 @@ "app.groupsStudent.title": "Joining Groups as Student", "app.groupsSupervisor.addAttributeModal.existingAttributes": "Existing attributes", "app.groupsSupervisor.addAttributeModal.title": "Add Attribute to Group", + "app.groupsSupervisor.archiveButton": "Archive Selected Groups", + "app.groupsSupervisor.archivingGroupsHeading": "Archiving Old Term Groups", + "app.groupsSupervisor.archivingGroupsInfo": "Selected term groups will be archived (with all their sub-groups). This operation may be reverted in ReCodEx by individually excavating the groups from the archive.", + "app.groupsSupervisor.cancelArchivingButton": "Cancel Archiving", "app.groupsSupervisor.cancelPlantTermButton": "Cancel Group Planting", "app.groupsSupervisor.currentlyManagedGroups": "Groups", "app.groupsSupervisor.notSuperadmin": "This page is available to ReCodEx administrators only.", @@ -101,6 +105,7 @@ "app.groupsSupervisor.plantTermGroupsSelectParents": "Please, select courses in which the term groups will be planted.", "app.groupsSupervisor.plantingFailed": "Planting has failed. Some of the groups could not be created. Their parent groups are marked below.", "app.groupsSupervisor.plantingSucceeded": "Total {count} {count, plural, one {group} other {groups}} have been successfully planted.", + "app.groupsSupervisor.startArchivingButton": "Select Groups for Archiving", "app.groupsSupervisor.title": "Manage All Groups and Their Associations", "app.groupsTeacher.aboutStudentsInfo": "The students are not automatically added to the group, but they can join the group themselves via SIS-CodEx extension.", "app.groupsTeacher.bindGroupInfo": "The selected event will be bound to an existing target group which you can select below.", diff --git a/src/pages/GroupsSuperadmin/GroupsSuperadmin.js b/src/pages/GroupsSuperadmin/GroupsSuperadmin.js index 84c2880..6cb298e 100644 --- a/src/pages/GroupsSuperadmin/GroupsSuperadmin.js +++ b/src/pages/GroupsSuperadmin/GroupsSuperadmin.js @@ -15,7 +15,14 @@ import AddAttributeForm, { INITIAL_VALUES as ADD_FORM_INITIAL_VALUES } from '../ import PlantTermGroupsForm, { initialValuesCreator as plantFormInitialValuesCreator, } from '../../components/forms/PlantTermGroupsForm'; -import Icon, { CloseIcon, GroupIcon, LoadingIcon, ManagementIcon, PlantIcon } from '../../components/icons'; +import Icon, { + ArchiveIcon, + CloseIcon, + GroupIcon, + LoadingIcon, + ManagementIcon, + PlantIcon, +} from '../../components/icons'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import Button, { TheButtonGroup } from '../../components/widgets/TheButton'; import Callout from '../../components/widgets/Callout'; @@ -39,16 +46,18 @@ import { isSuperadminRole } from '../../components/helpers/usersRoles.js'; import { getErrorMessage } from '../../locales/apiErrorMessages.js'; import { getGroups as getGroupsHelper } from '../../components/Groups/helpers.js'; +import { isEmptyObject } from '../../helpers/common.js'; + const DEFAULT_EXPIRATION = 7; // days // keep only courses (term parents) and term groups const plantingGroupFilter = group => group.attributes?.course?.length > 0 || group.attributes?.term?.length > 0; -const groupCheckboxPredicate = (group, term) => +const plantingCheckboxPredicate = (group, term) => group.attributes?.course?.length > 0 && !group.children.some(g => g.attributes?.term?.includes(`${term.year}-${term.term}`)); -const plantingCheckboxSelector = lruMemoize(term => group => groupCheckboxPredicate(group, term)); +const plantingCheckboxSelector = lruMemoize(term => group => plantingCheckboxPredicate(group, term)); const highlightClassGenerator = lruMemoize( term => group => (group.attributes?.term?.includes(`${term.year}-${term.term}`) ? 'text-success fw-bold' : '') @@ -57,122 +66,167 @@ const highlightClassGenerator = lruMemoize( const getPlantingCandidates = (groups, term) => { const candidates = {}; getGroupsHelper(groups, 'en', true) - .filter(g => groupCheckboxPredicate(g, term) && g.attributes?.['for-term']?.includes(`${term.term}`)) + .filter(g => plantingCheckboxPredicate(g, term) && g.attributes?.['for-term']?.includes(`${term.term}`)) .forEach(g => { candidates[g.id] = true; }); return candidates; }; +const getGroupsForArchiving = lruMemoize((groups, terms) => { + const nowTs = Date.now() / 1000; + const archivedTerms = {}; + terms.filter(t => t.archiveAfter && t.archiveAfter <= nowTs).forEach(t => (archivedTerms[`${t.year}-${t.term}`] = t)); + + const groupsForArchiving = {}; + Object.values(groups) + .filter(g => g.attributes?.term && g.attributes?.term.some(t => archivedTerms[t])) + .forEach(g => { + groupsForArchiving[g.id] = true; + }); + + return groupsForArchiving; +}); + +const archivingGroupFilter = group => group.attributes?.term?.length > 0; + +const archivingCheckboxSelector = lruMemoize(groups => group => group.id in groups); + class GroupsSuperadmin extends Component { + /* + * There are several modes the page can be operating in, which are controlled by the state properties: + * - Adding new attribute to a group (modalGroup is set) + * - Planting term groups stage 1 -- updating planted group texts in a modal. + * - Planting term groups stage 2 -- selecting parent groups before confirming planting + * - Archiving old term groups -- selecting groups to be archived + */ state = { + operationPending: false, // an async operation (adding attribute, planting groups, archiving groups) is pending + selectedGroups: null, // groups with checkboxes (if checkboxes are shown) + selectedGroupsCount: 0, // number of selected groups (for easy access without iterating over selectedGroups) modalGroup: null, - modalGroupPending: false, modalGroupError: null, - plantTerm: null, modalPlant: false, + plantTerm: null, plantTexts: null, - plantGroups: null, - plantGroupsCount: 0, - plantGroupsPending: false, plantGroupsErrors: null, plantedGroups: 0, + archiving: false, + }; + + pageInDefaultMode = () => + !this.state.modalGroup && !this.state.plantTexts && !this.state.modalPlant && !this.state.archiving; + + changeSelectedGroups = (id, newState) => { + if ( + this.state.selectedGroups && + !this.state.operationPending && + Boolean(this.state.selectedGroups[id]) !== Boolean(newState) + ) { + const selectedGroups = { ...this.state.selectedGroups, [id]: Boolean(newState) }; + const selectedGroupsCount = Object.values(selectedGroups).filter(v => v).length; + this.setState({ selectedGroups, selectedGroupsCount }); + } }; - openModalGroup = modalGroup => - this.setState({ modalGroup, modalGroupPending: false, modalGroupError: null, modalPlant: false }); + /* + * Adding new attribute to a group + */ + + openModalGroup = modalGroup => { + if (this.pageInDefaultMode() && !this.state.operationPending) { + this.setState({ modalGroup, operationPending: false, modalGroupError: null, modalPlant: false }); + } + }; closeModalGroup = () => this.setState({ modalGroup: null, - modalGroupPending: false, + operationPending: false, modalGroupError: null, }); + addAttributeFormSubmit = async values => { + if (this.state.modalGroup) { + const key = values.mode === 'other' ? values.key.trim() : values.mode; + const value = values.mode === 'other' ? values.value.trim() : values[values.mode].trim(); + try { + await this.props.addAttribute(this.state.modalGroup.id, key, value); + this.closeModalGroup(); + return undefined; // no error + } catch (err) { + return { [FORM_ERROR]: err?.message || err.toString() }; + } + } else { + return Promise.resolve(); + } + }; + + /* + * Planting term groups + */ + openModalPlant = (groups, term) => { - const plantGroups = getPlantingCandidates(groups, term); + if (!this.pageInDefaultMode() || this.state.operationPending) { + return; + } + + const selectedGroups = getPlantingCandidates(groups, term); this.setState({ modalPlant: true, modalGroup: null, - plantGroups, + selectedGroups, plantTexts: null, - plantGroupsCount: Object.keys(plantGroups).length, - plantGroupsPending: false, + selectedGroupsCount: Object.keys(selectedGroups).length, + operationPending: false, plantGroupsErrors: null, plantedGroups: 0, }); }; - closeModalPlant = () => this.setState({ modalPlant: false, plantGroups: null, plantGroupsCount: 0 }); + closeModalPlant = () => this.setState({ modalPlant: false, selectedGroups: null, selectedGroupsCount: 0 }); cancelGroupPlanting = () => { - if (!this.state.plantGroupsPending) { + if (!this.state.operationPending) { this.setState({ plantTexts: null, - plantGroups: null, - plantGroupsCount: 0, - plantGroupsPending: false, + selectedGroups: null, + selectedGroupsCount: 0, + operationPending: false, plantGroupsErrors: null, plantedGroups: 0, }); } }; - addAttributeFormSubmit = async values => { - if (this.state.modalGroup) { - const key = values.mode === 'other' ? values.key.trim() : values.mode; - const value = values.mode === 'other' ? values.value.trim() : values[values.mode].trim(); - try { - await this.props.addAttribute(this.state.modalGroup.id, key, value); - this.closeModalGroup(); - return undefined; // no error - } catch (err) { - return { [FORM_ERROR]: err?.message || err.toString() }; - } - } else { - return Promise.resolve(); - } - }; - plantTermGroupsFormSubmit = plantTexts => { this.setState({ plantTexts, - plantGroupsPending: false, + operationPending: false, plantGroupsErrors: null, plantedGroups: 0, modalPlant: false, }); }; - changePlantGroups = (id, newState) => { - if ( - this.state.plantGroups && - !this.state.plantGroupsPending && - Boolean(this.state.plantGroups[id]) !== Boolean(newState) - ) { - const plantGroups = { ...this.state.plantGroups, [id]: Boolean(newState) }; - const plantGroupsCount = Object.values(plantGroups).filter(v => v).length; - this.setState({ plantGroups, plantGroupsCount }); - } - }; - plantGroups = async term => { const { createTermGroup, reloadGroups, intl: { formatMessage }, } = this.props; - if (this.state.plantGroupsCount === 0) { + + if (this.state.selectedGroupsCount === 0 || this.state.operationPending) { return; } - this.setState({ plantGroupsPending: true, plantGroupsErrors: null, plantedGroups: 0 }); + this.setState({ operationPending: true, plantGroupsErrors: null, plantedGroups: 0 }); // start creating the groups const termId = `${term.year}-${term.term}`; const promises = {}; - Object.keys(this.state.plantGroups) - .filter(id => this.state.plantGroups[id]) + Object.keys(this.state.selectedGroups) + .filter(id => this.state.selectedGroups[id]) .forEach(id => { promises[id] = createTermGroup(id, termId, this.state.plantTexts); }); @@ -193,15 +247,48 @@ class GroupsSuperadmin extends Component { if (Object.keys(plantGroupsErrors).length > 0) { // still planting, but also show errors - this.setState({ plantGroupsErrors, plantGroups: {} }); + this.setState({ plantGroupsErrors, selectedGroups: {} }); } else { // terminate planing - this.setState({ plantTexts: null, plantGroups: null }); + this.setState({ plantTexts: null, selectedGroups: null }); } - this.setState({ plantGroupsPending: false, plantGroupsCount: 0, plantedGroups }); + this.setState({ operationPending: false, selectedGroupsCount: 0, plantedGroups }); }; + /* + * Archiving + */ + + startArchiving = (groups, terms) => { + if (this.pageInDefaultMode() && !this.state.operationPending) { + const selectedGroups = getGroupsForArchiving(groups, terms); + this.setState({ + archiving: true, + selectedGroups, + selectedGroupsCount: Object.keys(selectedGroups).length, + plantGroupsErrors: null, + plantedGroups: 0, + }); + } + }; + + archiveSelectedGroups = async () => { + if (this.state.selectedGroupsCount === 0 || this.state.operationPending) { + return; + } + + // TODO change the groups state in ReCodEx + + this.cancelArchiving(); + }; + + cancelArchiving = () => this.setState({ archiving: false, selectedGroups: null, selectedGroupsCount: 0 }); + + /* + * Lifecycle management and data loading + */ + componentDidMount() { this.props.loadAsync(this.props.loggedInUserId); } @@ -290,8 +377,8 @@ class GroupsSuperadmin extends Component { variant={this.state.plantGroupsErrors ? 'danger' : 'success'} size="sm" onClick={() => this.plantGroups(this.state.plantTerm || terms[0])} - disabled={this.state.plantGroupsCount === 0 || this.state.plantGroupsPending}> - {this.state.plantGroupsPending ? : } + disabled={this.state.selectedGroupsCount === 0 || this.state.operationPending}> + {this.state.operationPending ? : } @@ -309,6 +396,47 @@ class GroupsSuperadmin extends Component { )} + {this.state.archiving && ( + }> +
+ +
+ +

+ +

+ + + + + +
+ )} + {this.state.plantedGroups > 0 && (
- {terms && terms.length > 0 && !this.state.plantTexts && ( - - - - - {terms.map(term => ( - this.setState({ plantTerm: term })}> - - - ))} - - - )} - - {this.state.plantTexts && ( - + + {terms && terms.length > 0 && this.pageInDefaultMode() && ( + + + + + {terms.map(term => ( + this.setState({ plantTerm: term })}> + + + ))} + + + )} + + {this.state.plantTexts && ( + <> + + + + )} + + {this.pageInDefaultMode() && !isEmptyObject(getGroupsForArchiving(groups, terms)) && ( - - - )} + )} + {this.state.archiving && ( + <> + + + + )} +
@@ -408,7 +585,7 @@ class GroupsSuperadmin extends Component { size="xl" fullscreen="xl-down" onHide={this.closeModalGroup}> - + Date: Sun, 8 Feb 2026 00:00:08 +0100 Subject: [PATCH 2/2] Finishing archiving procedure on Groups Superadmin page. --- src/locales/cs.json | 3 + src/locales/en.json | 3 + .../GroupsSuperadmin/GroupsSuperadmin.js | 102 ++++++++++++++++-- src/redux/modules/groups.js | 10 ++ 4 files changed, 108 insertions(+), 10 deletions(-) diff --git a/src/locales/cs.json b/src/locales/cs.json index e19efc6..748e41e 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -93,6 +93,9 @@ "app.groupsSupervisor.archiveButton": "Archivovat vybrané skupiny", "app.groupsSupervisor.archivingGroupsHeading": "Archivace starých semestrálních skupin", "app.groupsSupervisor.archivingGroupsInfo": "Vybrané semestrální skupiny budou archivovány (včetně všech jejich podskupin). Tuto operaci lze v ReCodExu vrátit zpět individuálním vyjmutím skupin z archivu.", + "app.groupsSupervisor.archivingResult": "Celkem {archivedGroups} {archivedGroups, plural, one {skupina byla archivována} =2 {skupiny byly archivovány} =3 {skupiny byly archivovány} =4 {skupiny byly archivovány} other {skupin bylo archivováno}} , ale u {archivedErrors} {archivedErrors, plural, one {skupiny} other {skupin}} se archivace nezdařila. Prosíme, zkuste operaci znovu nebo zkuste archivovat skupiny jednotlivě v ReCodExu.", + "app.groupsSupervisor.archivingResultFailure": "Celkem {archivedErrors} {archivedErrors, plural, one {skupina selhala} =2 {skupiny selhaly} =3 {skupiny selhaly} =4 {skupiny selhaly} other {skupin selhalo}} při archivaci. Prosíme, zkuste operaci znovu nebo zkuste archivovat skupiny jednotlivě v ReCodExu.", + "app.groupsSupervisor.archivingResultSuccess": "Celkem {archivedGroups} {archivedGroups, plural, one {skupina byla archivována} =2 {skupiny byly archivovány} =3 {skupiny byly archivovány} =4 {skupiny byly archivovány} other {skupin bylo archivováno}}.", "app.groupsSupervisor.cancelArchivingButton": "Zrušit archivaci", "app.groupsSupervisor.cancelPlantTermButton": "Zrušit osazování skupin", "app.groupsSupervisor.currentlyManagedGroups": "Skupiny", diff --git a/src/locales/en.json b/src/locales/en.json index e7415ca..611be09 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -93,6 +93,9 @@ "app.groupsSupervisor.archiveButton": "Archive Selected Groups", "app.groupsSupervisor.archivingGroupsHeading": "Archiving Old Term Groups", "app.groupsSupervisor.archivingGroupsInfo": "Selected term groups will be archived (with all their sub-groups). This operation may be reverted in ReCodEx by individually excavating the groups from the archive.", + "app.groupsSupervisor.archivingResult": "Total {archivedGroups} {archivedGroups, plural, one {group} other {groups}} were successfully archived, but {archivedErrors} {archivedErrors, plural, one {group} other {groups}} failed. Please, try the operation again or try archiving the groups individually in ReCodEx.", + "app.groupsSupervisor.archivingResultFailure": "Selected {archivedErrors} {archivedErrors, plural, one {group} other {groups}} failed to be archived. Please, try the operation again or try archiving the groups individually in ReCodEx.", + "app.groupsSupervisor.archivingResultSuccess": "Total {archivedGroups} {archivedGroups, plural, one {group} other {groups}} were successfully archived.", "app.groupsSupervisor.cancelArchivingButton": "Cancel Archiving", "app.groupsSupervisor.cancelPlantTermButton": "Cancel Group Planting", "app.groupsSupervisor.currentlyManagedGroups": "Groups", diff --git a/src/pages/GroupsSuperadmin/GroupsSuperadmin.js b/src/pages/GroupsSuperadmin/GroupsSuperadmin.js index 6cb298e..71efebe 100644 --- a/src/pages/GroupsSuperadmin/GroupsSuperadmin.js +++ b/src/pages/GroupsSuperadmin/GroupsSuperadmin.js @@ -33,6 +33,7 @@ import { addGroupAttribute, removeGroupAttribute, createTermGroup, + setGroupArchived, } from '../../redux/modules/groups.js'; import { fetchAllTerms } from '../../redux/modules/terms.js'; import { fetchUserIfNeeded } from '../../redux/modules/users.js'; @@ -104,14 +105,16 @@ class GroupsSuperadmin extends Component { operationPending: false, // an async operation (adding attribute, planting groups, archiving groups) is pending selectedGroups: null, // groups with checkboxes (if checkboxes are shown) selectedGroupsCount: 0, // number of selected groups (for easy access without iterating over selectedGroups) - modalGroup: null, - modalGroupError: null, - modalPlant: false, - plantTerm: null, - plantTexts: null, - plantGroupsErrors: null, - plantedGroups: 0, - archiving: false, + modalGroup: null, // group for which the "add attribute" modal is open (also controls whether the modal is open) + modalGroupError: null, // error message to be displayed in the "add attribute" modal if the operation fails + modalPlant: false, // whether the "plant term groups" modal is open + plantTerm: null, // term selected for planting, if null, the first term in the list is used + plantTexts: null, // texts for the groups to be planted (not null indicates that stage 2 of planting is active) + plantGroupsErrors: null, // object groupId -> error message for groups that failed to be planted (only relevant in stage 2 of planting) + plantedGroups: 0, // number of groups successfully planted in the last planting operation + archiving: false, // whether the page is in "archiving mode" (selecting groups for archiving) + archivedGroups: 0, // number of groups successfully archived in the last archiving operation + archivedErrors: 0, // number of groups that failed to be archived in the last archiving operation }; pageInDefaultMode = () => @@ -135,7 +138,15 @@ class GroupsSuperadmin extends Component { openModalGroup = modalGroup => { if (this.pageInDefaultMode() && !this.state.operationPending) { - this.setState({ modalGroup, operationPending: false, modalGroupError: null, modalPlant: false }); + this.setState({ + modalGroup, + operationPending: false, + modalGroupError: null, + modalPlant: false, + plantedGroups: 0, + archivedGroups: 0, + archivedErrors: 0, + }); } }; @@ -181,6 +192,8 @@ class GroupsSuperadmin extends Component { operationPending: false, plantGroupsErrors: null, plantedGroups: 0, + archivedGroups: 0, + archivedErrors: 0, }); }; @@ -269,18 +282,42 @@ class GroupsSuperadmin extends Component { selectedGroupsCount: Object.keys(selectedGroups).length, plantGroupsErrors: null, plantedGroups: 0, + archivedGroups: 0, + archivedErrors: 0, }); } }; archiveSelectedGroups = async () => { + const { setGroupArchived, reloadGroups } = this.props; + if (this.state.selectedGroupsCount === 0 || this.state.operationPending) { return; } - // TODO change the groups state in ReCodEx + const promises = {}; + Object.keys(this.state.selectedGroups) + .filter(id => this.state.selectedGroups[id]) + .forEach(id => { + promises[id] = setGroupArchived(id, true); + }); + + // wait for all promises and handle errors + let archivedGroups = 0; + let archivedErrors = 0; + for (const id of Object.keys(promises)) { + try { + await promises[id]; + ++archivedGroups; + } catch (err) { + ++archivedErrors; + } + } + + await reloadGroups(); this.cancelArchiving(); + this.setState({ archivedGroups, archivedErrors }); }; cancelArchiving = () => this.setState({ archiving: false, selectedGroups: null, selectedGroupsCount: 0 }); @@ -460,6 +497,49 @@ class GroupsSuperadmin extends Component {
)} + {this.state.archivedGroups + this.state.archivedErrors > 0 && ( + + this.setState({ archivedGroups: 0, archivedErrors: 0 })} + className="float-end clickable pt-1" + /> + + {this.state.archivedErrors === 0 ? ( + + ) : this.state.archivedGroups === 0 ? ( + + ) : ( + + )} + + )} + dispatch(createTermGroup(parentId, term, texts)), reloadGroups: () => dispatch(fetchAllGroups()), + setGroupArchived: (groupId, value) => dispatch(setGroupArchived(groupId, value)), }) )(injectIntl(GroupsSuperadmin)); diff --git a/src/redux/modules/groups.js b/src/redux/modules/groups.js index cafe87b..9b4a109 100644 --- a/src/redux/modules/groups.js +++ b/src/redux/modules/groups.js @@ -13,6 +13,7 @@ export const additionalActionTypes = { ...createActionsWithPostfixes('JOIN', 'siscodex/groups'), ...createActionsWithPostfixes('ADD_ATTRIBUTE', 'siscodex/groups'), ...createActionsWithPostfixes('REMOVE_ATTRIBUTE', 'siscodex/groups'), + ...createActionsWithPostfixes('SET_ARCHIVED', 'siscodex/groups'), }; /** @@ -91,6 +92,15 @@ export const removeGroupAttribute = (groupId, key, value) => body: { key, value }, }); +export const setGroupArchived = (groupId, value) => + createApiAction({ + type: additionalActionTypes.SET_ARCHIVED, + endpoint: `/groups/${groupId}/archived`, + method: 'POST', + meta: { groupId, value }, + body: { value }, + }); + /** * Reducer */