Commit de39f952 authored by Nugraha, Sigit's avatar Nugraha, Sigit
Browse files

Initial group setting and member management for group admin

parent 615a3906
Pipeline #15153 passed with stage
in 2 minutes and 11 seconds
package org.gesis.stardat.ui.admin;
import java.util.Locale;
import javax.annotation.PostConstruct;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.spring.annotation.UIScope;
import org.gesis.stardat.service.*;
import org.gesis.stardat.ui.admin.layout.ManageGroupLayout;
import org.gesis.stardat.ui.admin.layout.ManageStudyGroupLayout;
......@@ -16,9 +15,8 @@ import org.vaadin.spring.events.EventBus;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.spring.annotation.SpringView;
import com.vaadin.spring.annotation.UIScope;
import javax.annotation.PostConstruct;
import java.util.Locale;
@UIScope
@SpringView( name = AdminView.VIEW_NAME )
......@@ -166,7 +164,7 @@ public class AdminView extends AbstractAdminView implements Translatable
mainContainer.add( manageUserLayout );
break;
case MANAGE_GROUP:
mainContainer.add( new ManageGroupLayout( i18n, groupService, userService, entityService ) );
mainContainer.add( new ManageGroupLayout( groupService, userService, entityService ) );
break;
case MANAGE_USER_ROLE:
mainContainer.add( new ManageUserRoleLayout( i18n, userService, roleService ) );
......
package org.gesis.stardat.ui.admin.form;
import java.util.List;
import java.util.Locale;
import com.vaadin.data.Binder;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.themes.ValoTheme;
import org.gesis.stardat.entity.StudyGroup;
import org.gesis.stardat.service.EntityCreationService;
import org.gesis.stardat.service.GroupService;
import org.gesis.stardat.service.I18N;
import org.gesis.stardat.service.UserService;
import org.gesis.stardat.service.dto.GroupDTO;
import org.gesis.stardat.service.dto.UserDTO;
import org.gesis.stardat.ui.admin.layout.ManageGroupLayout;
import org.gesis.stardat.ui.component.UploadGroupLogo;
import org.vaadin.dialogs.ConfirmDialog;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import org.vaadin.viritin.button.MButton;
import org.vaadin.viritin.fields.MTextField;
import com.vaadin.data.Binder;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.themes.ValoTheme;
import java.util.List;
import java.util.Locale;
public class GroupForm extends FormLayout implements Translatable {
......@@ -38,15 +37,13 @@ public class GroupForm extends FormLayout implements Translatable {
private final transient GroupService groupService;
private final transient UserService userService;
private final transient I18N i18n;
private GroupDTO groupDTO;
private ManageGroupLayout manageGroupLayout;
private Binder<GroupDTO> binder = new Binder<>(GroupDTO.class);
private final EntityCreationService creationService;
public GroupForm(I18N i18n, ManageGroupLayout manageGroupLayout, GroupService groupService, UserService userService, EntityCreationService creationService) {
this.i18n = i18n;
public GroupForm( ManageGroupLayout manageGroupLayout, GroupService groupService, UserService userService, EntityCreationService creationService) {
this.manageGroupLayout = manageGroupLayout;
this.groupService = groupService;
this.userService = userService;
......@@ -82,10 +79,10 @@ public class GroupForm extends FormLayout implements Translatable {
private void delete() {
ConfirmDialog.show( this.getUI(),
i18n.get( MSG_KEY + "confirm.delete.header"),
i18n.get( MSG_KEY + "confirm.delete.message", groupDTO.getName()),
i18n.get( MSG_KEY + "confirm.delete.ok"),
i18n.get( MSG_KEY + "confirm.delete.cancel"),
I18N.get( MSG_KEY + "confirm.delete.header"),
I18N.get( MSG_KEY + "confirm.delete.message", groupDTO.getName()),
I18N.get( MSG_KEY + "confirm.delete.ok"),
I18N.get( MSG_KEY + "confirm.delete.cancel"),
dialog -> {
if( dialog.isConfirmed() ) {
// remove all user first
......@@ -123,10 +120,10 @@ public class GroupForm extends FormLayout implements Translatable {
@Override
public void updateMessageStrings(Locale locale) {
name.withCaption( i18n.get( MSG_KEY + "name") );
description.setCaption( i18n.get( MSG_KEY + "description") );
save.withCaption( i18n.get( MSG_KEY + "button.save") );
delete.withCaption( i18n.get( MSG_KEY + "button.delete") );
name.withCaption( I18N.get( MSG_KEY + "name") );
description.setCaption( I18N.get( MSG_KEY + "description") );
save.withCaption( I18N.get( MSG_KEY + "button.save") );
delete.withCaption( I18N.get( MSG_KEY + "button.delete") );
}
}
package org.gesis.stardat.ui.admin.form;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import com.vaadin.data.provider.Query;
import com.vaadin.shared.ui.ValueChangeMode;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.themes.ValoTheme;
import org.gesis.stardat.service.GroupService;
import org.gesis.stardat.service.I18N;
import org.gesis.stardat.service.UserService;
import org.gesis.stardat.service.dto.GroupDTO;
import org.gesis.stardat.service.dto.UserDTO;
import org.gesis.stardat.ui.admin.layout.ManageGroupUserLayout;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import org.vaadin.viritin.button.MButton;
import org.vaadin.viritin.fields.MTextField;
import org.vaadin.viritin.grid.MGrid;
import com.vaadin.data.provider.Query;
import com.vaadin.shared.ui.ValueChangeMode;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.themes.ValoTheme;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class GroupUserForm extends FormLayout implements Translatable {
......@@ -33,7 +32,6 @@ public class GroupUserForm extends FormLayout implements Translatable {
private MButton save = new MButton("Save");
private MButton delete = new MButton("Delete");
private final transient I18N i18n;
private final transient GroupService groupService;
private final transient UserService userService;
......@@ -43,9 +41,8 @@ public class GroupUserForm extends FormLayout implements Translatable {
private MGrid<UserDTO> userGrid = new MGrid<>(UserDTO.class);
public GroupUserForm(I18N i18n, ManageGroupUserLayout manageGroupUserLayout, GroupService groupService, UserService userService) {
public GroupUserForm(ManageGroupUserLayout manageGroupUserLayout, GroupService groupService, UserService userService) {
super();
this.i18n = i18n;
this.manageGroupUserLayout = manageGroupUserLayout;
this.groupService = groupService;
this.userService = userService;
......@@ -91,8 +88,8 @@ public class GroupUserForm extends FormLayout implements Translatable {
save.setCaption("Add user to " + groupDTO.getName() + " group");
delete.setCaption("Remove user from " + groupDTO.getName() + " group");
save.withCaption( i18n.get( MSG_KEY + "button.save", groupDTO.getName()) );
delete.withCaption( i18n.get( MSG_KEY + "button.delete", groupDTO.getName()) );
save.withCaption( I18N.get( MSG_KEY + "button.save", groupDTO.getName()) );
delete.withCaption( I18N.get( MSG_KEY + "button.delete", groupDTO.getName()) );
}
public void setUserDTO(UserDTO userDTO, boolean isAddNewMember) {
......@@ -159,19 +156,19 @@ public class GroupUserForm extends FormLayout implements Translatable {
@Override
public void updateMessageStrings(Locale locale) {
name.withCaption( i18n.get( MSG_KEY + "name") );
groupName.withCaption( i18n.get( MSG_KEY + "groupName") );
name.withCaption( I18N.get( MSG_KEY + "name") );
groupName.withCaption( I18N.get( MSG_KEY + "groupName") );
filterText
.withCaption(i18n.get( MSG_KEY + "filter"))
.withPlaceholder(i18n.get( MSG_KEY + "filter.placeholder"));
.withCaption(I18N.get( MSG_KEY + "filter"))
.withPlaceholder(I18N.get( MSG_KEY + "filter.placeholder"));
save.withCaption( i18n.get( MSG_KEY + "button.save", groupDTO == null ? "": groupDTO.getName()) );
delete.withCaption( i18n.get( MSG_KEY + "button.delete", groupDTO == null ? "": groupDTO.getName()) );
save.withCaption( I18N.get( MSG_KEY + "button.save", groupDTO == null ? "": groupDTO.getName()) );
delete.withCaption( I18N.get( MSG_KEY + "button.delete", groupDTO == null ? "": groupDTO.getName()) );
userGrid.getColumn( "firstName" ).setCaption(i18n.get( MSG_KEY + "firstName") );
userGrid.getColumn( "lastName" ).setCaption(i18n.get( MSG_KEY + "lastName") );
userGrid.getColumn( "username" ).setCaption(i18n.get( MSG_KEY + "username") );
userGrid.getColumn( "firstName" ).setCaption(I18N.get( MSG_KEY + "firstName") );
userGrid.getColumn( "lastName" ).setCaption(I18N.get( MSG_KEY + "lastName") );
userGrid.getColumn( "username" ).setCaption(I18N.get( MSG_KEY + "username") );
}
}
package org.gesis.stardat.ui.admin.layout;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.ValueChangeMode;
import com.vaadin.ui.themes.ValoTheme;
import org.gesis.stardat.entity.StudyGroup;
import org.gesis.stardat.service.EntityCreationService;
import org.gesis.stardat.service.GroupService;
import org.gesis.stardat.service.I18N;
import org.gesis.stardat.service.UserService;
import org.gesis.stardat.service.dto.GroupDTO;
import org.gesis.stardat.ui.admin.form.GroupForm;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import org.vaadin.viritin.button.MButton;
import org.vaadin.viritin.fields.MTextField;
......@@ -19,10 +19,9 @@ import org.vaadin.viritin.label.MLabel;
import org.vaadin.viritin.layouts.MCssLayout;
import org.vaadin.viritin.layouts.MHorizontalLayout;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.ValueChangeMode;
import com.vaadin.ui.themes.ValoTheme;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class ManageGroupLayout extends MCssLayout implements Translatable {
private static final long serialVersionUID = 1L;
......@@ -30,7 +29,6 @@ public class ManageGroupLayout extends MCssLayout implements Translatable {
// autowired
private final transient GroupService groupService;
private final transient I18N i18n;
private final EntityCreationService creationService;
// components
......@@ -42,13 +40,12 @@ public class ManageGroupLayout extends MCssLayout implements Translatable {
private MButton addBtn = new MButton();
private GroupForm form;
public ManageGroupLayout(I18N i18n, GroupService gService, UserService uService, EntityCreationService creationService) {
public ManageGroupLayout(GroupService gService, UserService uService, EntityCreationService creationService) {
super();
this.i18n = i18n;
this.groupService = gService;
this.creationService = creationService;
this.form = new GroupForm(i18n, this, gService, uService, creationService);
this.groupUserLayout = new ManageGroupUserLayout(i18n, gService, uService);
this.form = new GroupForm(this, gService, uService, creationService);
this.groupUserLayout = new ManageGroupUserLayout(gService, uService);
init();
}
......@@ -129,14 +126,14 @@ public class ManageGroupLayout extends MCssLayout implements Translatable {
@Override
public void updateMessageStrings(Locale locale) {
pageTitle.withValue(i18n.get( MSG_KEY + "header"));
addBtn.setCaption(i18n.get( MSG_KEY + "add"));
pageTitle.withValue(I18N.get( MSG_KEY + "header"));
addBtn.setCaption(I18N.get( MSG_KEY + "add"));
filterText.withPlaceholder(i18n.get( MSG_KEY + "mainFilter.placeholder"));
clearFilterTextBtn.setDescription(i18n.get( MSG_KEY + "mainFilter.placeholder.clear"));
filterText.withPlaceholder(I18N.get( MSG_KEY + "mainFilter.placeholder"));
clearFilterTextBtn.setDescription(I18N.get( MSG_KEY + "mainFilter.placeholder.clear"));
grid.getColumn( "name" ).setCaption(i18n.get( MSG_KEY + "name") );
// grid.getColumn( "description" ).setCaption(i18n.get( MSG_KEY + "description") );
grid.getColumn( "name" ).setCaption(I18N.get( MSG_KEY + "name") );
// grid.getColumn( "description" ).setCaption(I18N.get( MSG_KEY + "description") );
form.updateMessageStrings(locale);
groupUserLayout.updateMessageStrings(locale);
......
package org.gesis.stardat.ui.admin.layout;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Grid;
import com.vaadin.ui.themes.ValoTheme;
import org.gesis.stardat.domain.enumeration.AuthProvider;
import org.gesis.stardat.service.GroupService;
import org.gesis.stardat.service.I18N;
import org.gesis.stardat.service.UserService;
import org.gesis.stardat.service.dto.GroupDTO;
import org.gesis.stardat.service.dto.UserDTO;
import org.gesis.stardat.ui.admin.form.GroupUserForm;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import org.vaadin.viritin.button.MButton;
import org.vaadin.viritin.grid.MGrid;
......@@ -19,16 +18,15 @@ import org.vaadin.viritin.layouts.MCssLayout;
import org.vaadin.viritin.layouts.MHorizontalLayout;
import org.vaadin.viritin.layouts.MVerticalLayout;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Grid;
import com.vaadin.ui.themes.ValoTheme;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
public class ManageGroupUserLayout extends MHorizontalLayout implements Translatable {
private static final long serialVersionUID = -2016450000939989815L;
private static final String MSG_KEY = "manage.group.user.";
private final transient I18N i18n;
private final transient UserService userService;
// components
......@@ -39,10 +37,9 @@ public class ManageGroupUserLayout extends MHorizontalLayout implements Translat
private GroupUserForm form;
private GroupDTO group;
public ManageGroupUserLayout(I18N i18n, GroupService gService, UserService uService) {
public ManageGroupUserLayout(GroupService gService, UserService uService) {
super();
this.i18n = i18n;
this.form = new GroupUserForm(i18n, this, gService, uService);
this.form = new GroupUserForm(this, gService, uService);
this.userService = uService;
......@@ -92,12 +89,12 @@ public class ManageGroupUserLayout extends MHorizontalLayout implements Translat
}
public void updateMessageStrings(Locale locale) {
pageTitle.withValue(i18n.get( MSG_KEY + "header", group == null ? "" : group.getName()));
addBtn.setCaption(i18n.get( MSG_KEY + "add", group == null ? "" : group.getName()));
pageTitle.withValue(I18N.get( MSG_KEY + "header", group == null ? "" : group.getName()));
addBtn.setCaption(I18N.get( MSG_KEY + "add", group == null ? "" : group.getName()));
grid.getColumn( "firstName" ).setCaption(i18n.get( MSG_KEY + "firstName") );
grid.getColumn( "lastName" ).setCaption(i18n.get( MSG_KEY + "lastName") );
grid.getColumn( "username" ).setCaption(i18n.get( MSG_KEY + "username") );
grid.getColumn( "firstName" ).setCaption(I18N.get( MSG_KEY + "firstName") );
grid.getColumn( "lastName" ).setCaption(I18N.get( MSG_KEY + "lastName") );
grid.getColumn( "username" ).setCaption(I18N.get( MSG_KEY + "username") );
form.updateMessageStrings(locale);
}
......@@ -110,8 +107,8 @@ public class ManageGroupUserLayout extends MHorizontalLayout implements Translat
.collect(Collectors.toList());
grid.setItems(userDTOs);
pageTitle.withValue(i18n.get( MSG_KEY + "header", group == null ? "" : group.getName()));
addBtn.setCaption(i18n.get( MSG_KEY + "add", group == null ? "" : group.getName()));
pageTitle.withValue(I18N.get( MSG_KEY + "header", group == null ? "" : group.getName()));
addBtn.setCaption(I18N.get( MSG_KEY + "add", group == null ? "" : group.getName()));
this.setVisible( true );
form.setVisible( false );
form.setGroupDTO( groupDTO );
......
package org.gesis.stardat.ui.admin.window;
import com.vaadin.data.Binder;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TextArea;
import org.gesis.stardat.entity.StudyGroup;
import org.gesis.stardat.service.EntityCreationService;
import org.gesis.stardat.service.GroupService;
import org.gesis.stardat.service.I18N;
import org.gesis.stardat.service.UserService;
import org.gesis.stardat.service.dto.GroupDTO;
import org.gesis.stardat.ui.component.UploadGroupLogo;
import org.vaadin.viritin.button.MButton;
import org.vaadin.viritin.fields.MTextField;
import org.vaadin.viritin.layouts.MCssLayout;
import org.vaadin.viritin.layouts.MWindow;
public class ManageGroupWindow extends MWindow
{
private static final long serialVersionUID = 3756056172287573134L;
public static final String WINDOW_NAME = "ManageGroupWindow";
private MCssLayout manageGroupLayout = new MCssLayout().withFullWidth();
private MCssLayout layoutContent = new MCssLayout();
private MTextField name = new MTextField("Name");
private TextArea description = new TextArea("Description");
private MTextField email = new MTextField("Group e-mail");
private UploadGroupLogo uploadGroupLogo = new UploadGroupLogo();
private GroupDTO groupDTO;
private final transient GroupService groupService;
private final transient UserService userService;
private final transient EntityCreationService creationService;
private Binder<GroupDTO> binder = new Binder<>(GroupDTO.class);
private MButton save = new MButton("Save");
public ManageGroupWindow(GroupService groupService, UserService userService, EntityCreationService creationService, GroupDTO groupDTO, StudyGroup studyGroup )
{
super( I18N.get( ManageGroupWindow.WINDOW_NAME + ".title") );
this.groupService = groupService;
this.userService = userService;
this.creationService = creationService;
this.groupDTO = groupDTO;
binder.bindInstanceFields(this);
binder.setBean(groupDTO);
uploadGroupLogo.init(groupDTO);
HorizontalLayout buttons = new HorizontalLayout(save);
uploadGroupLogo.setCaption("Header logo");
manageGroupLayout.addComponents(name, description, email, uploadGroupLogo, buttons);
layoutContent.add(manageGroupLayout);
this
.withWidth("1024px")
.withHeight("800px")
.withModal( true )
.withCenter()
.withContent(layoutContent);
}
private void save() {
if( groupDTO == null || groupDTO.getName().isEmpty())
return;
if( groupDTO.getId() == null ) {
final StudyGroup newStudyGroup = creationService.createNewStudyGroup(groupDTO.getName(), groupDTO.getDescription());
groupDTO.setGroupReference( newStudyGroup.getId());
} else {
// TODO: store modification
}
groupService.save(groupDTO);
}
}
......@@ -21,8 +21,10 @@ import org.gesis.stardat.helper.TriggerableWindowMaster;
import org.gesis.stardat.security.DDIEditorSecurityUtils;
import org.gesis.stardat.security.oauth2.StardatOAuth2User;
import org.gesis.stardat.service.*;
import org.gesis.stardat.service.dto.GroupDTO;
import org.gesis.stardat.service.dto.StudyRoleDTO;
import org.gesis.stardat.ui.QuestionnaireEditorView;
import org.gesis.stardat.ui.admin.window.ManageGroupWindow;
import org.gesis.stardat.ui.view.AccessDeniedView;
import org.gesis.stardat.ui.view.component.StudyUnitGrid;
import org.gesis.stardat.ui.view.questionnaire.CreateQuestionnaireWindow;
......@@ -326,10 +328,13 @@ public class StudySelectionView extends VerticalLayout implements View, Translat
questionnairesGrid.setColumns( LABEL );
hSplitPanel.addComponents( studiesLayout, previewPanel );
groupsGrid.setSizeFull();
groupsGrid.addComponentColumn( this::generateManageGroupButton );
groupsGrid.setHeight( "100px" );
groupsGrid.setColumns( LABEL );
groupsGrid.removeAllColumns();
groupsGrid.addColumn(sg -> sg.getLabel()).setCaption("label");
groupsGrid.addComponentColumn( this::generateManageGroupButton ).setWidth( 100 );
getStudyUnitGrid().setSizeFull();
getStudyUnitGrid().setHeight( "300px" );
......@@ -355,15 +360,20 @@ public class StudySelectionView extends VerticalLayout implements View, Translat
eventBus.subscribe( this );
}
private MButton generateManageGroupButton( StudyGroup variable )
private MButton generateManageGroupButton( StudyGroup studyGroup )
{
MButton bManageGroup = new MButton()
.withIcon( VaadinIcons.USERS )
.withStyleName( ValoTheme.BUTTON_SMALL, "pull-right", "btn-spacing-large" );
.withStyleName( ValoTheme.BUTTON_SMALL, "pull-right", "btn-spacing-large" )
.withDescription("Manage Group");
bManageGroup.setVisible(false);
bManageGroup.addClickListener( e ->
{
final GroupDTO groupDTO = groupService.findByGroupReference(studyGroup.getId());
ManageGroupWindow mgw = new ManageGroupWindow(groupService, userService, entityService, groupDTO, studyGroup);
UI.getCurrent().addWindow( mgw );
} );
return bManageGroup;
}
......@@ -798,9 +808,9 @@ public class StudySelectionView extends VerticalLayout implements View, Translat
newStudyButton.setCaption( I18N.get( SELECTION_VIEW_STUDY_BUTTON_NEW_STUDY ) );
cloneQuestionnaireButton.setCaption( I18N.get( SELECTION_VIEW_STUDY_BUTTON_CLONE_QUESTIONNAIRE ) );
cloneStudyButton.setCaption( I18N.get( SELECTION_VIEW_STUDY_BUTTON_CLONE_STUDY ) );
groupsGrid.setCaption( I18N.get( "SelectionView.Group.Table.label.title" ) );
groupsGrid.setDescription( I18N.get( "SelectionView.Group.Table.description" ) );
groupsGrid.getColumn(LABEL).setCaption(I18N.get("StudySelectionView.groups.column.caption"));
// groupsGrid.setCaption( I18N.get( "SelectionView.Group.Table.label.title" ) );
// groupsGrid.setDescription( I18N.get( "SelectionView.Group.Table.description" ) );
// groupsGrid.getColumn(LABEL).setCaption(I18N.get("StudySelectionView.groups.column.caption"));
questionnairesGrid.getColumn(LABEL).setCaption(I18N.get("StudySelectionView.questionnaires.column.caption"));
getStudyUnitGrid().setCaption( I18N.get( "SelectionView.Study.Table.label.title" ) );
getStudyUnitGrid().setDescription( I18N.get( "SelectionView.Study.Table.description" ) );
......
......@@ -56,6 +56,7 @@ tab.publication.name = Veröffentlichungen
tab.content.name = Inhalt
tab.groups.name = Gruppen
ManageGroupWindow.title = Manage Study Group {0}
#StudyEditor Bibliogaphy
......
......@@ -57,6 +57,8 @@ CategoryView.freeVariabel.name = Loose Variable
CategoryView.freeVariabel.button.edit = edit
CategoryView.freeVariabel.button.save = save
ManageGroupWindow.title = Manage Study Group {0}
CategoryView.CategoryVariable.TableColumn.fid = Father ID
CategoryView.CategoryVariable.TableColumn.id = ID
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment