public class Associate {
String id;
String name;
+ String login;
+ String password;
double hourlyRate;
boolean active;
}
private List<Associate> generate() {
List<Associate> result = new ArrayList<>();
- for (int i = 0; i < 100; i++) {
+ for (int i = 0; i < 10; i++) {
String id = RandomStringUtils.random(8, "0123456789abcdef");
String name = dataGeneratorService.faker().name().fullName();
-
- Associate entity = Associate.builder().active(true).id(id).name(name).build();
+ String login = "user" + (i + 1);
+ String password = "password";
+ Associate entity = Associate.builder().active(true).id(id).name(name).login(login).password(password).build();
result.add(entity);
}
return result;
public String projects() {
return "index";
}
+
+ @GetMapping("/associates")
+ public String associates() {
+ return "index";
+ }
}
--- /dev/null
+package hu.user.lis.ui.data;
+
+import hu.user.lis.db.Associate;
+import hu.user.lis.services.data.AssociateService;
+import lombok.Getter;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.zkoss.bind.BindUtils;
+import org.zkoss.zul.FieldComparator;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+@Log4j2
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class AssociatesDataModel extends CachedDataModel<Associate> {
+ @Getter
+ @Autowired
+ AssociateService associateService;
+ private String partialName;
+ private boolean listAll;
+ private boolean filterShowInActive;
+ private boolean filterShowActive;
+
+
+ private boolean canExecuteSearch() {
+ boolean result = listAll || filterShowActive || filterShowInActive ||
+ StringUtils.isNotBlank(partialName);
+ log.info("Can execute search: {}", result);
+ return result;
+ }
+
+ private boolean filter(Associate associate) {
+ if (listAll) {
+ return true;
+ }
+
+ boolean result = true;
+ if (StringUtils.isNotBlank(partialName)) {
+ if (!associate.getName().toLowerCase().startsWith(partialName.toLowerCase())) {
+ result = false;
+ }
+ }
+ if (!filterShowActive && associate.isActive()) {
+ result = false;
+ }
+
+ if (!filterShowInActive && !associate.isActive()) {
+ result = false;
+ }
+
+ return result;
+ }
+
+ @Override
+ protected List<Associate> getResultSet(long offset, int limit, FieldComparator sortComparator) {
+ List<Associate> result = null;
+ if (canExecuteSearch()) {
+ result = associateService.getAll().stream()
+ .filter(s -> filter(s))
+ .sorted(Comparator.comparing(Associate::getName, String.CASE_INSENSITIVE_ORDER))
+ .collect(Collectors.toList());
+ }
+ return result;
+ }
+
+ @Override
+ public int getResultSetCount() {
+ int result = 0;
+ if (canExecuteSearch()) {
+ result = (int) associateService.getAll().stream()
+ .filter(s -> filter(s))
+ .count();
+ }
+ return result;
+ }
+
+ public void search(String partialName) {
+ log.info("Searching ssociate using filters: name LIKE {}",
+ partialName);
+ listAll = false;
+ this.partialName = partialName;
+ super.reset();
+ BindUtils.postNotifyChange(null, null, this, "*");
+ }
+
+
+ public void search(boolean filterShowActive, boolean filterShowInActive) {
+ log.info("Searching partner using filters: filterShowActive {}, filterShowInActive {}",
+ filterShowActive, filterShowInActive);
+ this.filterShowActive = filterShowActive;
+ this.filterShowInActive = filterShowInActive;
+ listAll = false;
+ super.reset();
+ BindUtils.postNotifyChange(null, null, this, "*");
+ }
+
+ public void listAll() {
+ log.info("List all associates");
+ listAll = true;
+ super.reset();
+ BindUtils.postNotifyChange(null, null, this, "*");
+ }
+}
--- /dev/null
+package hu.user.lis.ui.view;
+
+import hu.user.lis.db.Associate;
+import hu.user.lis.services.data.AssociateService;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang3.StringUtils;
+import org.zkoss.bind.BindUtils;
+import org.zkoss.bind.ValidationContext;
+import org.zkoss.bind.annotation.BindingParam;
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.Init;
+import org.zkoss.bind.validator.AbstractValidator;
+import org.zkoss.zk.ui.Component;
+import org.zkoss.zk.ui.Executions;
+import org.zkoss.zk.ui.event.Event;
+import org.zkoss.zk.ui.event.Events;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zkplus.spring.DelegatingVariableResolver;
+import org.zkoss.zul.Window;
+
+import java.util.Objects;
+
+@Log4j2
+@Getter
+@Setter
+@VariableResolver(DelegatingVariableResolver.class)
+public class AssociateEditorModel extends AbstractValidator {
+ @WireVariable
+ AssociateService associateServiceImpl;
+ private Associate formDocument;
+ private Associate origDocument;
+ private boolean formInvalid = true;
+
+ @Init
+ public void init() {
+ log.info("Initialized");
+ origDocument = (Associate) Executions.getCurrent().getArg().get("origDocument");
+ formDocument = (Associate) Executions.getCurrent().getArg().get("formDocument");
+ }
+
+ @Command
+ public void onCloseWindow(@BindingParam("target") Window target, @BindingParam("select") boolean select) {
+ if (select && formInvalid) {
+ return;
+ }
+ Event closeEvent = new Event("onClose", target, select ? formDocument : null);
+ Events.postEvent(closeEvent);
+ }
+
+ @Override
+ public void validate(ValidationContext ctx) {
+ Component target = ctx.getBindContext().getComponent();
+ String property = ctx.getProperty().getProperty();
+ Object value = ctx.getProperty().getValue();
+ log.info("Validating caused by {} {} {}", target.getId(), property, value);
+ updateFormInvalid(false);
+ try {
+ Associate newData = associateServiceImpl.copy(formDocument, property, value);
+ if (!Objects.isNull(origDocument) && associateServiceImpl.toString(origDocument).equals(associateServiceImpl.toString(newData))) {
+ log.info("Document not changed");
+ updateFormInvalid(true);
+ return;
+ }
+ if (StringUtils.isBlank(newData.getName()) ||
+ StringUtils.isBlank(newData.getLogin()) ||
+ StringUtils.isBlank(newData.getPassword()) ||
+ newData.getHourlyRate() < 1
+ ) {
+ log.info("Document is not valid");
+ updateFormInvalid(true);
+ }
+
+
+ } catch (Exception e) {
+ log.catching(e);
+ }
+ }
+
+ private void updateFormInvalid(boolean invalid) {
+ setFormInvalid(invalid);
+ BindUtils.postNotifyChange(this, "formInvalid");
+ }
+}
--- /dev/null
+package hu.user.lis.ui.view;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import hu.user.lis.db.Associate;
+import hu.user.lis.ui.data.AssociatesDataModel;
+import lombok.Getter;
+import lombok.extern.log4j.Log4j2;
+import org.zkoss.bind.annotation.*;
+import org.zkoss.zk.ui.Component;
+import org.zkoss.zk.ui.Executions;
+import org.zkoss.zk.ui.select.Selectors;
+import org.zkoss.zk.ui.select.annotation.VariableResolver;
+import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zk.ui.util.Clients;
+import org.zkoss.zkplus.spring.DelegatingVariableResolver;
+import org.zkoss.zul.Window;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+@Log4j2
+@VariableResolver(DelegatingVariableResolver.class)
+public class AssociatesViewModel {
+ @WireVariable
+ @Getter
+ AssociatesDataModel associatesDataModel;
+ @Getter
+ private Associate selectedEntity;
+ private boolean filterShowInActive;
+ private boolean filterShowActive;
+ private boolean filterShowBoth;
+
+ @AfterCompose
+ public void onAfterCompose(@ContextParam(ContextType.VIEW) Component view) {
+ Selectors.wireComponents(view, this, false);
+ Selectors.wireEventListeners(view, this);
+ }
+
+ @Init
+ public void init() {
+ setFilterShowActive(true);
+ log.info("Initialized");
+ Clients.evalJavaScript("pushNav('/associates')");
+ }
+
+ private void refresh() {
+ associatesDataModel.clearSelection();
+ selectedEntity = null;
+ if (filterShowBoth) {
+ associatesDataModel.search(true, true);
+ } else {
+ associatesDataModel.search(filterShowActive, filterShowInActive);
+ }
+ }
+
+ @Command
+ @NotifyChange("selectedPartner")
+ public void search() {
+ }
+
+ @Command
+ @NotifyChange("selectedPartner")
+ public void onListSelection() {
+ selectedEntity = null;
+ Set<Associate> selections = associatesDataModel.getSelection();
+ if (selections.size() == 1) {
+ selectedEntity = selections.iterator().next();
+ log.info("Selected {}", selectedEntity);
+ }
+ }
+
+ @Command
+ public void onAfterRenderList() {
+ }
+
+ @Command
+ public void onAdd() {
+ String page = "~./associate-editor.zul";
+ Associate newEntity = associatesDataModel.getAssociateService().createNew();
+ Window editorWindow = (Window) Executions.createComponents(page, null,
+ Collections.singletonMap("formDocument", newEntity));
+ editorWindow.addEventListener("onClose", e -> {
+ if (e.getData() != null) {
+ log.info("Associate popup result {}", e.getData());
+ associatesDataModel.getAssociateService().add(newEntity);
+ associatesDataModel.clearSelection();
+ refresh();
+ associatesDataModel.addToSelection(newEntity);
+ selectedEntity = newEntity;
+ }
+ });
+ editorWindow.doModal();
+ }
+
+ @Command
+ public void onEdit() throws JsonProcessingException {
+ String page = "~./associate-editor.zul";
+ Associate editEntity = associatesDataModel.getAssociateService().copy(selectedEntity);
+ Map<String, Object> arg = new HashMap<>();
+ arg.put("origDocument", selectedEntity);
+ arg.put("formDocument", editEntity);
+ Window editorWindow = (Window) Executions.createComponents(page, null, arg);
+ editorWindow.addEventListener("onClose", e -> {
+ if (e.getData() != null) {
+ log.info("Partner popup result {}", e.getData());
+ Associate modifiedEntity = (Associate) e.getData();
+ associatesDataModel.clearSelection();
+ associatesDataModel.getAssociateService().replace(selectedEntity, modifiedEntity);
+ refresh();
+ associatesDataModel.addToSelection(modifiedEntity);
+ selectedEntity = modifiedEntity;
+ }
+ });
+ editorWindow.doModal();
+ }
+
+
+ public boolean isFilterShowInActive() {
+ return filterShowInActive;
+ }
+
+ @NotifyChange({"filterShowActive", "filterShowInActive", "filterShowBoth"})
+ public void setFilterShowInActive(boolean filterShowInActive) {
+ this.filterShowBoth = false;
+ this.filterShowActive = false;
+ this.filterShowInActive = filterShowInActive;
+ refresh();
+ }
+
+ public boolean isFilterShowActive() {
+ return filterShowActive;
+ }
+
+ @NotifyChange({"filterShowActive", "filterShowInActive", "filterShowBoth"})
+ public void setFilterShowActive(boolean filterShowActive) {
+ this.filterShowBoth = false;
+ this.filterShowInActive = false;
+ this.filterShowActive = filterShowActive;
+ refresh();
+ }
+
+ public boolean isFilterShowBoth() {
+ return this.filterShowBoth;
+ }
+
+ @NotifyChange({"filterShowActive", "filterShowInActive", "filterShowBoth"})
+ public void setFilterShowBoth(boolean filterShowBoth) {
+ this.filterShowActive = false;
+ this.filterShowInActive = false;
+ this.filterShowBoth = filterShowBoth;
+ refresh();
+ }
+}
public static final String PROJECT_EDITOR_PAGE = "~./project-editor.zul";
private static final String PARTNERS_LIST = "~./partners.zul";
private static final String PROJECTS_LIST = "~./projects.zul";
+ private static final String ASSOCIATES_LIST = "~./associates.zul";
@WireVariable
BuildProperties buildProperties;
@WireVariable
String searchPhrase;
String page;
private Map<String, String> navigation = ImmutableMap.of(
- "/projects", PROJECTS_LIST
+ "/projects", PROJECTS_LIST,
+ "/accociates", ASSOCIATES_LIST
);
@Init
import org.zkoss.zk.ui.select.annotation.VariableResolver;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zkplus.spring.DelegatingVariableResolver;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Window;
@Init
public void init() {
- log.info("Initialized");
setFilterShowActive(true);
+ Clients.evalJavaScript("pushNav('/')");
+ log.info("Initialized");
}
private void refresh() {
import org.zkoss.zk.ui.select.Selectors;
import org.zkoss.zk.ui.select.annotation.VariableResolver;
import org.zkoss.zk.ui.select.annotation.WireVariable;
+import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zkplus.spring.DelegatingVariableResolver;
import java.util.Map;
public void init() {
setFilterShowActive(true);
eventBus.register(this);
+ Clients.evalJavaScript("pushNav('/projects')");
log.info("Initialized");
}
--- /dev/null
+<?link rel="stylesheet" type="text/css" href="~./static/css/skeleton.css" ?>
+<?link rel="stylesheet" type="text/css" href="~./static/css/webclient.css" ?>
+<zk>
+ <window id="associatePopup" width="60%" height="40%" closable="true"
+ viewModel="@id('vm') @init('hu.user.lis.ui.view.AssociateEditorModel')">
+ <caption label="Munkatárs szerkesztés"/>
+ <borderlayout>
+ <center border="none" vflex="true" hflex="true">
+ <tabbox vflex="true" hflex="true">
+ <tabs>
+ <tab label="Adatok" selected="true"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel>
+ <vlayout hflex="true">
+ <label value="Név"/>
+ <textbox hflex="true" instant="true" value="@bind(vm.formDocument.name) @validator(vm)"
+ forward="onOK=submit.onClick, onCancel=cancel.onClick"/>
+ <label value="Login"/>
+ <textbox hflex="true" instant="true" value="@bind(vm.formDocument.login) @validator(vm)"
+ forward="onOK=submit.onClick, onCancel=cancel.onClick"/>
+ <label value="Jelszó"/>
+ <hlayout>
+ <textbox hflex="true" instant="true" type="password"
+ value="@bind(vm.formDocument.password) @validator(vm)"
+ forward="onOK=submit.onClick, onCancel=cancel.onClick"/>
+ <button iconSclass="z-icon-eye"/>
+ </hlayout>
+ <label value="Óradíj"/>
+ <doublebox value="@bind(vm.formDocument.hourlyRate) @validator(vm)"
+ format="#.##" instant="true"
+ forward="onOK=submit.onClick, onCancel=cancel.onClick"/>
+ <label value="Aktív"/>
+ <checkbox mold="switch" checked="@bind(vm.formDocument.active)"/>
+ </vlayout>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+ </center>
+ <south border="none" flex="true" style="text-align: right; padding: 10px">
+ <hlayout>
+ <button id="cancel" label="Bezár"
+ onClick="@command('onCloseWindow', target=associatePopup, select=false)"/>
+ <button id="submit" label="Mentés"
+ onClick="@command('onCloseWindow', target=associatePopup, select=true)"
+ disabled="@bind(vm.formInvalid)"/>
+ </hlayout>
+ </south>
+ </borderlayout>
+ </window>
+</zk>
\ No newline at end of file
--- /dev/null
+<?link rel="stylesheet" type="text/css" href="~./static/css/skeleton.css" ?>
+<?link rel="stylesheet" type="text/css" href="~./static/css/webclient.css" ?>
+<zk>
+ <style>
+ .z-listitem-selected>.z-listcell>.z-listcell-content {
+ font-weight: bold;
+ }
+ </style>
+ <window vflex="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.AssociatesViewModel')">
+ <caption label="Munkatársak"/>
+ <borderlayout>
+ <north flex="true">
+ <toolbar>
+ <toolbarbutton label="Hozzáadás" iconSclass="z-icon-plus" onClick="@command('onAdd')"/>
+ <toolbarbutton label="Szerkesztés" iconSclass="z-icon-edit" onClick="@command('onEdit')"
+ disabled="@load(empty vm.selectedEntity)"/>
+ <separator orient="vertical"/>
+ <toolbarbutton mode="toggle" iconSclass="z-icon-check" label="Aktív"
+ checked="@bind(vm.filterShowActive)"/>
+ <toolbarbutton mode="toggle" iconSclass="z-icon-ban" label="Inaktív"
+ checked="@bind(vm.filterShowInActive)"/>
+ <toolbarbutton mode="toggle" iconSclass="z-icon-adjust" label="Mind"
+ checked="@bind(vm.filterShowBoth)"/>
+ </toolbar>
+ </north>
+ <center border="none" flex="true">
+ <listbox vflex="true" model="@load(vm.associatesDataModel)"
+ autopaging="true" pagingPosition="top" multiple="false"
+ onSelect="@command('onListSelection')" onDoubleClick="@command('onEdit')"
+ onAfterRender="@command('onAfterRenderList')">
+ <custom-attributes org.zkoss.zul.listbox.selectOnHighlight.disabled="true"/>
+
+ <listhead>
+ <listheader label="Név" align="left"/>
+ <listheader label="Login" align="left"/>
+ <listheader label="Aktív" align="left"/>
+ </listhead>
+ <template name="model">
+ <listitem>
+ <listcell label="@load(each.name)"/>
+ <listcell label="@load(each.login)"/>
+ <listcell>
+ <a iconSclass="z-icon-check" visible="@load(each.active)"/>
+ <a iconSclass="z-icon-ban" visible="@load(!each.active)"/>
+ </listcell>
+ </listitem>
+ </template>
+ </listbox>
+ </center>
+ </borderlayout>
+
+ </window>
+</zk>
\ No newline at end of file
import hu.user.lis.db.Currency;
ListModelList currencies = new ListModelList(Currency.values());
</zscript>
- <window id="invoicePopup" title="Bejövő számla szerkesztés" width="50%" height="50%" closable="true"
+ <window id="invoicePopup" width="50%" height="50%" closable="true"
maximizable="true" sizable="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.InvoiceEditorModel')">
+ <caption label="Bejövő számla szerkesztés"/>
<borderlayout>
<center border="none" vflex="true" hflex="true">
<tabbox vflex="true" hflex="true">
font-weight: bolder;
}
</style>
+ <script>
+ function pushNav(nav) {
+ history.pushState({}, "", nav);
+ }
+ </script>
<window vflex="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.IndexViewModel')">
<caption>
<hlayout valign="middle">
<north border="none" hflex="true">
<hlayout valign="middle">
<menubar autodrop="true" hflex="true">
- <menuitem id="partnersMenuItem" iconSclass="z-icon-group" label="Partnerek"
+ <menuitem iconSclass="z-icon-group" label="Partnerek"
onClick="@command(vm.selectPage('~./partners.zul'))"/>
- <menuitem id="projectsMenuItem" iconSclass="z-icon-tasks" label="Projektek"
+ <menuitem iconSclass="z-icon-tasks" label="Projektek"
onClick="@command(vm.selectPage('~./projects.zul'))"/>
+ <menuitem iconSclass="z-icon-th" label="Munkalapok" disabled="true"
+ onClick="@command(vm.selectPage('~./worksheets.zul'))"/>
+ <menuseparator/>
+ <menuitem iconSclass="z-icon-user" label="Munkatársak"
+ onClick="@command(vm.selectPage('~./associates.zul'))"/>
</menubar>
<hbox hflex="min" pack="right">
<textbox value="@bind(vm.searchPhrase)" onOK="@command('search')"></textbox>
import hu.user.lis.db.Currency;
ListModelList currencies = new ListModelList(Currency.values());
</zscript>
- <window id="invoicePopup" title="Kimenő számla szerkesztés" width="50%" height="50%" closable="true"
+ <window id="invoicePopup" width="50%" height="50%" closable="true"
maximizable="true" sizable="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.InvoiceEditorModel')">
+ <caption label="Kimenő számla szerkesztés"/>
<borderlayout>
<center border="none" vflex="true" hflex="true">
<tabbox vflex="true" hflex="true">
<?link rel="stylesheet" type="text/css" href="~./static/css/skeleton.css" ?>
<?link rel="stylesheet" type="text/css" href="~./static/css/webclient.css" ?>
<zk>
- <window id="partnerPopup" title="Partner szerkesztés" width="60%" height="40%" closable="true"
+ <window id="partnerPopup" width="60%" height="40%" closable="true"
viewModel="@id('vm') @init('hu.user.lis.ui.view.PartnerEditorModel')">
+ <caption label="Partner szerkesztés"/>
<borderlayout>
<center border="none" vflex="true" hflex="true">
<tabbox vflex="true" hflex="true">
font-weight: bold;
}
</style>
- <window title="Partnerek" vflex="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.PartnersViewModel')">
+ <window vflex="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.PartnersViewModel')">
<!-- <timer id="timer" delay="500" repeats="true" onTimer="@command('uiTick')"/>-->
+ <caption label="Partnerek"/>
<borderlayout>
<north flex="true">
<toolbar>
<?link rel="stylesheet" type="text/css" href="~./static/css/webclient.css" ?>
<?component name="partner-selector" inline="true" macroURI="~./partner-selector.zul"?>
<zk>
- <window id="projectEditor" title="Projekt szerkesztés" hflex="true" vflex="true"
+ <window id="projectEditor" hflex="true" vflex="true"
viewModel="@id('vm') @init('hu.user.lis.ui.view.ProjectEditorModel')">
+ <caption label="Projekt szerkesztés"/>
<borderlayout>
<center id="centerPanel" border="none" vflex="true" hflex="true" autoscroll="true">
<vlayout hflex="true" vflex="min">
<?link rel="stylesheet" type="text/css" href="~./static/css/webclient.css" ?>
<?component name="partner-selector" inline="true" macroURI="~./partner-selector.zul"?>
<zk>
- <window id="projectEditor" title="Projekt szerkesztés" width="60%" height="80%" closable="true"
+ <window id="projectEditor" width="60%" height="80%" closable="true"
viewModel="@id('vm') @init('hu.user.lis.ui.view.ProjectEditorModel')">
+ <caption label="Projekt szerkesztés"/>
<borderlayout>
<center border="none" vflex="true" hflex="true">
<tabbox vflex="true" hflex="true">
font-weight: bold;
}
</style>
- <script>
- history.pushState({}, "", "/projects");
- </script>
- <window title="Projektek" vflex="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.ProjectsViewModel')">
+ <window vflex="true" viewModel="@id('vm') @init('hu.user.lis.ui.view.ProjectsViewModel')">
+ <caption label="Projektek"/>
<borderlayout>
<north flex="true">
<toolbar>