From 2bf6f741816d631017faa6f1b7299a2587550883 Mon Sep 17 00:00:00 2001 From: Shaun Chyxion Date: Sun, 18 Mar 2018 22:15:30 +0800 Subject: [PATCH] add image preview --- .../controller/AbstractBaseController.java | 203 +++++++++++-- .../crm/controller/ApplicationController.java | 42 ++- .../crm/controller/BaseQueryController.java | 68 +++-- .../crm/controller/CustomerController.java | 275 ++++++------------ .../controller/CustomerIssueController.java | 14 +- .../CustomerPropertyController.java | 19 +- .../crm/controller/HomePageController.java | 15 +- .../controller/ImportRecordController.java | 16 +- .../crm/controller/UserController.java | 44 +-- .../crm/controller/WeekGoalController.java | 12 +- .../support/ApplicationServiceSupport.java | 41 ++- .../crm/mapper/ApplicationImageMapper.java | 8 + .../crm/mapper/ApplicationImageMapper.xml | 7 + .../ambition/crm/mapper/ApplicationMapper.xml | 18 ++ .../form/update/ApplicationFormForUpdate.java | 2 + .../ambition/crm/model/Application.java | 7 + .../customer-application/preview-btn.js | 21 ++ web/app/components/form-input-select2.js | 5 +- web/app/components/image-previews.js | 19 +- web/app/components/modal-dialog.js | 16 +- web/app/components/option-text.js | 3 +- web/app/components/sortable-th.js | 42 +-- .../helpers/customer-application-images.js | 16 + web/app/routes/base-list.js | 5 +- web/app/routes/customer-application/create.js | 6 +- web/app/routes/customer-application/edit.js | 15 +- web/app/routes/customer-application/list.js | 14 + web/app/routes/customer/list.js | 3 - .../customer-application/preview-btn.hbs | 18 ++ .../templates/components/image-previews.hbs | 29 +- web/app/templates/components/modal-dialog.hbs | 16 +- web/app/templates/components/th-filter.hbs | 5 +- .../templates/customer-application/create.hbs | 12 +- .../templates/customer-application/edit.hbs | 34 ++- .../templates/customer-application/list.hbs | 34 ++- .../customer-application/preview-btn-test.js | 24 ++ .../customer-application-images-test.js | 17 ++ 37 files changed, 767 insertions(+), 378 deletions(-) create mode 100644 web/app/components/customer-application/preview-btn.js create mode 100644 web/app/helpers/customer-application-images.js create mode 100644 web/app/templates/components/customer-application/preview-btn.hbs create mode 100644 web/tests/integration/components/customer-application/preview-btn-test.js create mode 100644 web/tests/integration/helpers/customer-application-images-test.js diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/AbstractBaseController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/AbstractBaseController.java index c7ae75e..eeacf6f 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/AbstractBaseController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/AbstractBaseController.java @@ -1,17 +1,20 @@ package com.pudonghot.ambition.crm.controller; -import java.util.Map; +import java.util.*; import java.net.URLDecoder; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; +import java.lang.reflect.Array; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; +import me.chyxion.tigon.model.M1; import org.apache.commons.io.IOUtils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import me.chyxion.tigon.mybatis.Search; import com.alibaba.fastjson.JSONException; +import com.alibaba.fastjson.util.TypeUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.StringUtils; import java.io.UnsupportedEncodingException; import org.apache.commons.lang3.CharEncoding; @@ -107,12 +110,11 @@ public abstract class AbstractBaseController { */ protected InputStream getInputStream(MultipartFile file) { try { - return file == null || file.isEmpty() ? - null : file.getInputStream(); + return file != null && !file.isEmpty() ? file.getInputStream() : null; } catch (IOException e) { throw new IllegalStateException( - "Read Multipart File Stream Error Caused", e); + "Read multipart file stream error caused", e); } } @@ -123,13 +125,13 @@ public abstract class AbstractBaseController { protected String decodeParam(String param) { if (StringUtils.isNotBlank(param)) { try { - log.debug("Decode Param [{}].", param); + log.debug("Decode param [{}].", param); param = URLDecoder.decode(param, CharEncoding.UTF_8); - log.debug("Decode Param Result [{}].", param); + log.debug("Decode param result [{}].", param); } catch (UnsupportedEncodingException e) { throw new IllegalStateException( - "Decode [" + param + "] Error Caused", e); + "Decode [" + param + "] error caused", e); } } return param; @@ -139,14 +141,12 @@ public abstract class AbstractBaseController { * @param start start * @param limit limit * @param search search - * @param strSorters sorters * @return search */ protected Search search(final Integer start, final Integer limit, - final String search, - final String strSorters) { - return search(null, start, limit, search, strSorters); + final String search) { + return search(null, start, limit, search, null, null, null); } /** @@ -155,14 +155,16 @@ public abstract class AbstractBaseController { * @param start start * @param limit limit * @param strSearch search - * @param strSorters sorters + * @param strOrders orders * @return search */ protected Search search(Search search, final Integer start, final Integer limit, final String strSearch, - final String strSorters) { + final String strCriteria, + final String strFilters, + final String strOrders) { if (search == null) { search = new Search(); @@ -175,27 +177,140 @@ public abstract class AbstractBaseController { } if (StringUtils.isNotBlank(strSearch)) { final Search orSearch = new Search(); - for (String col : searchCols()) { + for (final String col : searchCols()) { orSearch.or(new Search().like(col, decodeLike(strSearch))); } search.and(orSearch); } - if (StringUtils.isNotBlank(strSorters)) { - Map sorterCols = sorterCols(); - final JSONArray jaSorters; + criteria(search, strCriteria); + filters(search, strFilters); + order(search, strOrders) ; + return search; + } + + protected Search criteria(final Search search, final String strCriteria) { + if (StringUtils.isBlank(strCriteria)) { + log.debug("No criteria given."); + return search; + } + + final List criteria; + try { + criteria = JSON.parseArray(decodeParam(strCriteria), Object[].class); + } + catch (Exception e) { + throw new IllegalStateException( + "Invalid criteria [" + strCriteria + "]", e); + } + + for (final Object[] criterion : criteria) { + if (criterion.length == 3) { + final String field = (String) criterion[0]; + if (StringUtils.isNotBlank(field)) { + final Pair> colProp = criterionCol(field); + final String col = colProp.getKey(); + if (StringUtils.isNotBlank(col)) { + final String op = (String) criterion[1]; + final Object val = TypeUtils.castToJavaBean(criterion[2], colProp.getValue()); + if (StringUtils.isNotBlank(op) && + val != null && val instanceof String ? + StringUtils.isNotBlank((String) val) : true) { + onSearch(search, col); + if ("eq".equals(op)) { + search.eq(col, val); + } + else if ("gt".equals(op)) { + search.gt(col, val); + } + else if ("gte".equals(op)) { + search.gte(col, val); + } + else if ("lt".equals(op)) { + search.lt(col, val); + } + else if ("lte".equals(op)) { + search.lt(col, val); + } + else if ("ne".equals(op)) { + search.ne(col, val); + } + else if ("like".equals(op)) { + search.like(col, (String) val, true); + } + else { + log.warn("Unknown criterion op [{}], ignore.", op); + } + } + } + } + else { + log.warn("Criterion col is blank, ignore."); + } + } + else { + log.warn("Invalid criterion [{}], ignore.", criterion); + } + } + return search; + } + + protected JSONObject filters(String filters) { + try { + return JSON.parseObject(decodeParam(filters)); + } + catch (Exception e) { + throw new IllegalStateException( + "Invalid filters [" + filters + "]", e); + } + } + + protected Search filters(final Search search, final String strFilters) { + + if (StringUtils.isBlank(strFilters)) { + log.debug("No filters given, ignore."); + return defaultFilter(search); + } + + final JSONObject joFilters = filters(strFilters); + for (Map.Entry filter : joFilters.entrySet()) { + final String field = filter.getKey(); + final Pair> colProp = filterCol(filter.getKey()); + if (colProp != null) { + final String col = colProp.getKey(); + final Object filterVal = filter.getValue(); + if (filterVal != null) { + onSearch(search, col); + // type convert + if (filterVal instanceof JSONArray) { + search.eq(col, joFilters.getObject( + field, Array.newInstance(colProp.getValue(), 0).getClass())); + } + else { + search.eq(col, filterVal); + } + } + } + return search; + } + return search; + } + + protected Search order(final Search search, final String strOrders) { + if (StringUtils.isNotBlank(strOrders)) { + final JSONArray jaOrders; try { - jaSorters = JSON.parseArray(decodeParam(strSorters)); + jaOrders = JSON.parseArray(decodeParam(strOrders)); } catch (JSONException e) { throw new IllegalStateException( - "Invalid Sorters JSONArray Params [" + strSorters + "]", e); + "Invalid orders params [" + strOrders + "]", e); } - for (Object objSorter : jaSorters) { - final JSONObject joSorter = (JSONObject) objSorter; + for (final Object objOrder : jaOrders) { + final JSONObject joSorter = (JSONObject) objOrder; if (!joSorter.isEmpty()) { final Map.Entry sortEntry = joSorter.entrySet().iterator().next(); - final String col = sorterCols.get(sortEntry.getKey()); + final String col = orderCol(sortEntry.getKey()); if (StringUtils.isNotBlank(col)) { final String dir = (String) sortEntry.getValue(); if ("asc".equalsIgnoreCase(dir)) { @@ -208,20 +323,48 @@ public abstract class AbstractBaseController { } } } + else { + defaultOrder(search); + } return search; } /** * @return simple search cols */ - protected String[] searchCols() { - return new String[]{ }; + protected List searchCols() { + return Collections.emptyList(); } - /** - * @return simple search cols - */ - protected Map sorterCols() { - return Collections.emptyMap(); + protected Search defaultFilter(final Search search) { + return search; + } + + protected Search defaultOrder(final Search search) { + return search; + } + + protected Pair> criterionCol(final String field) { + return null; + } + + protected Pair> filterCol(final String field) { + return null; + } + + protected String orderCol(final String field) { + return null; + } + + protected void onSearch(final Search search, final String col) { + + } + + protected Map defaultOrderCols() { + final Map cols = new HashMap<>(); + cols.put(M1.ID, M1.ID); + cols.put("dateCreated", M1.DATE_CREATED); + cols.put("dateUpdated", M1.DATE_UPDATED); + return cols; } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java index ca3ab38..dd4357c 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java @@ -1,5 +1,7 @@ package com.pudonghot.ambition.crm.controller; +import java.util.List; +import java.util.Arrays; import javax.validation.Valid; import me.chyxion.tigon.mybatis.Search; import javax.validation.constraints.Max; @@ -8,14 +10,18 @@ import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ListResult; import org.apache.commons.lang3.StringUtils; import com.pudonghot.ambition.crm.model.User; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Controller; import com.pudonghot.ambition.crm.model.Application; +import com.pudonghot.ambition.crm.service.UserService; import org.apache.shiro.authz.annotation.RequiresRoles; import com.pudonghot.ambition.crm.model.CustomerProperty; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestParam; import com.pudonghot.ambition.crm.service.ApplicationService; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.beans.factory.annotation.Autowired; import com.pudonghot.ambition.crm.form.create.ApplicationFormForCreate; import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate; @@ -29,6 +35,14 @@ import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate; public class ApplicationController extends BaseQueryController { + @Value("${file.base-path}") + private String fileBasePath; + @Autowired + private UserService userService; + private final List SEARCH_COLS = + Arrays.asList(CustomerProperty.NAME, + CustomerProperty.NOTE); + @RequestMapping("/list") public ListResult> list( @Min(0) @@ -38,6 +52,8 @@ public class ApplicationController @Max(2048) @RequestParam(value = "limit", defaultValue = "16") final int limit, + @RequestParam(value = "filters", required = false) + final String filters, @RequestParam(value = "search", required = false) final String strSearch) { @@ -47,8 +63,12 @@ public class ApplicationController if (!user.isAdmin()) { search.eq(Application.OWNER, user.getId()); } - - return listViewModels(search, start, limit, strSearch, null); + final ListResult> result = + listViewModels(search, start, limit, strSearch, null, filters, null); + result.setAttr("fileBasePath", fileBasePath); + result.setAttr("users", userService.listViewModels( + new Search().asc(User.EMPLOYEE_ID))); + return result; } @RequestMapping("/list-for-select") @@ -63,7 +83,7 @@ public class ApplicationController if (StringUtils.isNotBlank(enabled)) { search.eq(CustomerProperty.ENABLED, Boolean.parseBoolean(enabled)); } - return listViewModels(search, null, null, strSearch, null); + return listViewModels(search, null, null, strSearch, null, null, null); } @RequiresRoles(User.ROLE_ADMIN) @@ -77,6 +97,7 @@ public class ApplicationController @RequestMapping(value = "/update", method = RequestMethod.POST) public void update( @Valid ApplicationFormForUpdate form) { + form.setAdmin(getAuthUser().getUser().getData().isAdmin()); ((ApplicationService) queryService).update(form); } @@ -84,10 +105,15 @@ public class ApplicationController * {@inheritDoc} */ @Override - protected String[] searchCols() { - return new String[] { - CustomerProperty.NAME, - CustomerProperty.NOTE - }; + protected List searchCols() { + return SEARCH_COLS; + } + + /** + * {@inheritDoc} + */ + @Override + protected Pair> filterCol(final String field) { + return Application.OWNER.equals(field) ? Pair.of(field, String.class) : null; } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/BaseQueryController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/BaseQueryController.java index 966fd97..24832a4 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/BaseQueryController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/BaseQueryController.java @@ -1,11 +1,12 @@ package com.pudonghot.ambition.crm.controller; +import lombok.extern.slf4j.Slf4j; import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.model.BaseModel; import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ListResult; -import javax.validation.constraints.NotNull; import me.chyxion.tigon.service.BaseQueryService; +import org.hibernate.validator.constraints.NotBlank; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,12 +18,31 @@ import org.springframework.web.bind.annotation.RequestMapping; * chyxion@163.com
* May 10, 2016 4:50:58 PM */ +@Slf4j public class BaseQueryController> extends BaseController { @Autowired protected BaseQueryService queryService; + /** + * find model by id + * @param id model id + * @return model + */ + @RequestMapping("/find") + public ViewModel find(@NotBlank @RequestParam("id") String id) { + return queryService.findViewModel(id); + } + + /** + * @return models count + */ + @RequestMapping("/count") + public int count() { + return queryService.count(null); + } + /** * list view models * @param start start @@ -34,40 +54,48 @@ public class BaseQueryController> final Integer start, final Integer limit, final String paramSearch) { - return listViewModels(start, limit, paramSearch, null); + return listViewModels(start, limit, paramSearch, null, null, null); } /** - * list view models * @param start start * @param limit limit * @param strSearch search - * @param strSorters sorters - * @return search + * @param strCriteria criteria + * @param strFilters filters + * @param strOrders orders + * @return list result */ protected ListResult> listViewModels( final Integer start, final Integer limit, final String strSearch, - final String strSorters) { - return listViewModels(null, start, limit, strSearch, strSorters); + final String strCriteria, + final String strFilters, + final String strOrders) { + return listViewModels(null, start, limit, strSearch, strCriteria, strFilters, strOrders); } /** - * list view models * @param search search * @param start start * @param limit limit * @param strSearch search - * @return view models + * @param strCriteria criteria + * @param strFilters filters + * @param strOrders orders + * @return list result */ protected ListResult> listViewModels( final Search search, final Integer start, final Integer limit, final String strSearch, - final String strSorters) { - return listViewModels(search(search, start, limit, strSearch, strSorters)); + final String strCriteria, + final String strFilters, + final String strOrders) { + return listViewModels(search( + search, start, limit, strSearch, strCriteria, strFilters, strOrders)); } /** @@ -78,22 +106,4 @@ public class BaseQueryController> protected ListResult> listViewModels(Search search) { return queryService.listViewModelsPage(search); } - - /** - * find model by id - * @param id model id - * @return model - */ - @RequestMapping("/find") - public ViewModel find(@NotNull @RequestParam("id") String id) { - return queryService.findViewModel(id); - } - - /** - * @return models count - */ - @RequestMapping("/count") - public int count() { - return queryService.count(null); - } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerController.java index 2721168..44898e7 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerController.java @@ -2,9 +2,8 @@ package com.pudonghot.ambition.crm.controller; import java.util.*; import javax.validation.Valid; -import com.alibaba.fastjson.JSON; + import lombok.extern.slf4j.Slf4j; -import com.alibaba.fastjson.JSONObject; import me.chyxion.tigon.mybatis.Search; import me.chyxion.tigon.model.ViewModel; import javax.validation.constraints.Max; @@ -13,6 +12,7 @@ import me.chyxion.tigon.model.ListResult; import javax.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; import com.pudonghot.ambition.crm.model.User; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Controller; import com.pudonghot.ambition.crm.model.Customer; import org.apache.shiro.authz.annotation.RequiresRoles; @@ -37,7 +37,7 @@ import com.pudonghot.ambition.crm.form.update.CustomerFormForUpdate; public class CustomerController extends BaseQueryController { - private static final String[] SEARCH_COLS = new String[] { + private static final List SEARCH_COLS = Arrays.asList( "customer.id", "customer.name", "customer.city", @@ -48,26 +48,25 @@ public class CustomerController "customer.salesperson", "year", // status text - "prop.name" - }; + "prop.name"); - private static final Map SORT_COLS = new HashMap<>(); + private static final Map ORDER_COLS = new HashMap<>(); static { - SORT_COLS.put(Customer.ID, "customer.id"); - SORT_COLS.put("dateAdded", "year(customer.date_added)"); - SORT_COLS.put(CustomerYearToDateSale.YEAR, CustomerYearToDateSale.YEAR); - SORT_COLS.put("countryCode", "customer.country_code"); - SORT_COLS.put(Customer.MS, "customer.ms"); - SORT_COLS.put(Customer.REGION, "customer.region"); - SORT_COLS.put(Customer.SALESPERSON, "customer.salesperson"); - SORT_COLS.put("sumYtdSales", Customer.SUM_YTD_SALES); - SORT_COLS.put("status", "prop.sort"); - SORT_COLS.put("application", "application_names"); + ORDER_COLS.put(Customer.ID, "customer.id"); + ORDER_COLS.put("dateAdded", "year(customer.date_added)"); + ORDER_COLS.put(CustomerYearToDateSale.YEAR, CustomerYearToDateSale.YEAR); + ORDER_COLS.put("countryCode", "customer.country_code"); + ORDER_COLS.put(Customer.MS, "customer.ms"); + ORDER_COLS.put(Customer.REGION, "customer.region"); + ORDER_COLS.put(Customer.SALESPERSON, "customer.salesperson"); + ORDER_COLS.put("sumYtdSales", Customer.SUM_YTD_SALES); + ORDER_COLS.put("status", "prop.sort"); + ORDER_COLS.put("application", "application_names"); } private static final Map CRITERION_COLS; static { - CRITERION_COLS = new HashMap<>(SORT_COLS); + CRITERION_COLS = new HashMap<>(ORDER_COLS); CRITERION_COLS.put(Customer.NAME, "customer.name"); CRITERION_COLS.put(Customer.STATE, "customer.state"); CRITERION_COLS.put(Customer.CITY, "customer.city"); @@ -77,6 +76,48 @@ public class CustomerController CRITERION_COLS.put("issue", "issue.artificial"); } + // filters + private static final Map>> FILTER_COLS = new HashMap<>(); + static { + FILTER_COLS.put("dateAdded", + Pair.of(CRITERION_COLS.get("dateAdded"), Integer.class)); + FILTER_COLS.put("year", + Pair.of(CRITERION_COLS.get("year"), String.class)); + FILTER_COLS.put("ms", + Pair.of(CRITERION_COLS.get("ms"), String.class)); + FILTER_COLS.put("region", + Pair.of(CRITERION_COLS.get("region"), String.class)); + FILTER_COLS.put("countryCode", + Pair.of(CRITERION_COLS.get("countryCode"), String.class)); + FILTER_COLS.put("state", + Pair.of(CRITERION_COLS.get("state"), String.class)); + FILTER_COLS.put("city", + Pair.of(CRITERION_COLS.get("city"), String.class)); + FILTER_COLS.put("salesperson", + Pair.of(CRITERION_COLS.get("city"), String.class)); + FILTER_COLS.put("status", + Pair.of(CRITERION_COLS.get("status"), String.class)); + FILTER_COLS.put("application", + Pair.of(CRITERION_COLS.get("application"), String.class)); + FILTER_COLS.put("issue", + Pair.of(CRITERION_COLS.get("issue"), Boolean.class)); + +// filter(search, joFilters, "dateAdded", Integer[].class); +// filter(search, joFilters, "year"); +// +// filter(search, joFilters, "ms"); +// filter(search, joFilters, "region"); +// filter(search, joFilters, "countryCode"); +// filter(search, joFilters, "state"); +// filter(search, joFilters, "city"); +// filter(search, joFilters, "salesperson"); +// filter(search, joFilters, "status"); +// filter(search, joFilters, "application"); +// +// filter(search, joFilters, "issue", Boolean[].class); + + } + @RequestMapping("/list") public ListResult> list( @Min(0) @@ -92,10 +133,10 @@ public class CustomerController final String criteria, @RequestParam(value = "filters", required = false) final String filters, - @RequestParam(value = "sorters", required = false) - final String sorters) { + @RequestParam(value = "orders", required = false) + final String orders) { - final Search search = filters(new Search(), filters); + final Search search = new Search(); User user = getUser().getData(); // if (!user.isAdmin()) { search.setAttr(User.ACCOUNT, user.getAccount()); @@ -104,9 +145,7 @@ public class CustomerController if (StringUtils.isNotBlank(strSearch)) { search.setAttr("YTD_SALE", true); } - final ListResult> result = - listViewModels(search(criteria(search, criteria), start, limit, strSearch, sorters)); - return result; + return listViewModels(search, start, limit, strSearch, criteria, filters, orders); } @RequiresRoles(User.ROLE_ADMIN) @@ -135,7 +174,7 @@ public class CustomerController * {@inheritDoc} */ @Override - protected String[] searchCols() { + protected List searchCols() { return SEARCH_COLS; } @@ -143,174 +182,42 @@ public class CustomerController * {@inheritDoc} */ @Override - protected Map sorterCols() { - return SORT_COLS; + protected Pair> filterCol(final String field) { + return FILTER_COLS.get(field); } - private String criterionCol(String col) { - return CRITERION_COLS.get(col); + /** + * {@inheritDoc} + */ + protected Pair> criterionCol(final String col) { + return Pair.of(CRITERION_COLS.get(col), Object.class); } - private Search criteria(final Search search, final String strCriteria) { - - if (StringUtils.isBlank(strCriteria)) { - log.debug("No Criteria Given."); - return search; - } - - final List criteria; - try { - criteria = JSON.parseArray(decodeParam(strCriteria), String[].class); - } - catch (Exception e) { - throw new IllegalStateException( - "Invalid Criteria [" + strCriteria + "]", e); - } - - for (String[] criterion : criteria) { - if (criterion.length == 3) { - final String col = criterion[0]; - if (StringUtils.isNotBlank(col)) { - final String criterionCol = criterionCol(col); - if (StringUtils.isNotBlank(criterionCol)) { - // for search year - if (CustomerYearToDateSale.YEAR.equals(criterionCol) || - CustomerYearToDateSale.YTD_SALE.equals(criterionCol)) { - search.setAttr("YTD_SALE", true); - } - final String op = criterion[1]; - final String val = criterion[2]; - if (StringUtils.isNotBlank(op) && StringUtils.isNotBlank(val)) { - if ("eq".equals(op)) { - search.eq(criterionCol, val); - } - else if ("gt".equals(op)) { - search.gt(criterionCol, val); - } - else if ("gte".equals(op)) { - search.gte(criterionCol, val); - } - else if ("lt".equals(op)) { - search.lt(criterionCol, val); - } - else if ("lte".equals(op)) { - search.lt(criterionCol, val); - } - else if ("ne".equals(op)) { - search.ne(criterionCol, val); - } - else if ("like".equals(op)) { - search.like(criterionCol, val, true); - } - else { - log.warn("Unknown Criterion Op [{}], Ignore.", op); - } - } - } - } - else { - log.warn("Criterion Col Is Blank, Ignore."); - } - } - else { - log.warn("Invalid Criterion [{}], Ignore.", criterion); - } - } - return search; + /** + * {@inheritDoc} + */ + @Override + protected String orderCol(final String field) { + return ORDER_COLS.get(field); } - private Search filters(final Search search, final String strFilters) { - - if (StringUtils.isBlank(strFilters)) { - log.debug("No filters given."); - defaultFilter(search, "status"); - return search; - } - - final JSONObject joFilters; - try { - joFilters = JSON.parseObject(decodeParam(strFilters)); - } - catch (Exception e) { - throw new IllegalStateException( - "Invalid Filters [" + strFilters + "]", e); - } - - filter(search, joFilters, "dateAdded", Integer[].class); - filter(search, joFilters, "year"); - filter(search, joFilters, "ms"); - filter(search, joFilters, "region"); - filter(search, joFilters, "countryCode"); - filter(search, joFilters, "state"); - filter(search, joFilters, "city"); - filter(search, joFilters, "salesperson"); - filter(search, joFilters, "status"); - filter(search, joFilters, "application"); - filter(search, joFilters, "issue", Boolean[].class); - - return search; + /** + * {@inheritDoc} + */ + @Override + protected Search defaultFilter(final Search search) { + return search.ne(CRITERION_COLS.get("status"), CustomerProperty.STATUS_NA_ID); } - private Search filter(final Search search, final JSONObject joFilters, final String field) { - return filter(search, joFilters, field, String[].class); - } - - private Search filter(final Search search, final JSONObject joFilters, final String field, Class clazz) { - final String col = CRITERION_COLS.get(field); - if (StringUtils.isNotBlank(col)) { - final T[] filters = - joFilters.getObject(field, clazz); - if (filters != null && filters.length > 0) { - log.info("Col [{}] filters [{}] found.", col, filters); - if ("application".equals(col)) { - search.setAttr("APPLICATIONS", filters); - } - else { - in(search, col, filters); - } - } - else { - defaultFilter(search, field); - } - - // filter year - if (CustomerYearToDateSale.YEAR.equals(col)) { - search.setAttr("YTD_SALE", true); - } - } - return search; - } - - private void defaultFilter(final Search search, final String field) { - // default no NA - if ("status".equals(field)) { - search.ne(CRITERION_COLS.get(field), CustomerProperty.STATUS_NA_ID); - } - } - - private void in(final Search search, final String col, final Object[] values) { - final Set setValues = new HashSet<>(Arrays.asList(values)); - final Iterator valIt = setValues.iterator(); - if (setValues.size() == 1) { - final Object val = valIt.next(); - search.eq(col, "null".equals(val) ? null : val); - } - else { - boolean nullValue = false; - while (valIt.hasNext()) { - final Object val = valIt.next(); - if (val == null || "null".equals(val)) { - nullValue = true; - valIt.remove(); - break; - } - } - if (nullValue) { - search.and(new Search(col, setValues).or(col, null)); - } - else { - search.in(col, setValues); - } + /** + * {@inheritDoc} + */ + @Override + protected void onSearch(final Search search, final String col) { + // for search year + if (CustomerYearToDateSale.YEAR.equals(col) || + CustomerYearToDateSale.YTD_SALE.equals(col)) { + search.setAttr("YTD_SALE", true); } } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerIssueController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerIssueController.java index 39f1370..fef8db9 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerIssueController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerIssueController.java @@ -1,5 +1,7 @@ package com.pudonghot.ambition.crm.controller; +import java.util.List; +import java.util.Arrays; import javax.validation.Valid; import javax.validation.constraints.Max; import me.chyxion.tigon.model.ViewModel; @@ -24,6 +26,11 @@ import com.pudonghot.ambition.crm.form.update.CustomerIssueFormForUpdate; public class CustomerIssueController extends BaseQueryController { + private static final List SEARCH_COLS = + Arrays.asList( + CustomerIssue.ISSUE, + CustomerIssue.NOTE); + @RequestMapping("/list") public ListResult> list( @Min(0) @@ -54,10 +61,7 @@ public class CustomerIssueController * {@inheritDoc} */ @Override - protected String[] searchCols() { - return new String[] { - CustomerIssue.ISSUE, - CustomerIssue.NOTE - }; + protected List searchCols() { + return SEARCH_COLS; } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerPropertyController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerPropertyController.java index 741e2a5..af48d51 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerPropertyController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/CustomerPropertyController.java @@ -21,6 +21,9 @@ import com.pudonghot.ambition.crm.service.CustomerPropertyService; import com.pudonghot.ambition.crm.form.create.CustomerPropertyFormForCreate; import com.pudonghot.ambition.crm.form.update.CustomerPropertyFormForUpdate; +import java.util.Arrays; +import java.util.List; + /** * @author Shaun Chyxion
* chyxion@163.com
@@ -31,6 +34,11 @@ import com.pudonghot.ambition.crm.form.update.CustomerPropertyFormForUpdate; public class CustomerPropertyController extends BaseQueryController { + private static final List SEARCH_COLS = + Arrays.asList( + CustomerProperty.NAME, + CustomerProperty.NOTE); + @RequestMapping("/list") public ListResult> list( @Min(0) @@ -54,7 +62,7 @@ public class CustomerPropertyController Boolean.parseBoolean(enabled)); } ((CustomerPropertyService) queryService).initSystemStatus(getUserId()); - return listViewModels(search, start, limit, strSearch, null); + return listViewModels(search, start, limit, strSearch, null, null, null); } @RequestMapping("/app-list") @@ -70,7 +78,7 @@ public class CustomerPropertyController if (StringUtils.isNotBlank(enabled)) { search.eq(CustomerProperty.ENABLED, Boolean.parseBoolean(enabled)); } - return listViewModels(search, null, null, strSearch, null); + return listViewModels(search, null, null, strSearch, null, null, null); } @RequiresRoles(User.ROLE_ADMIN) @@ -100,10 +108,7 @@ public class CustomerPropertyController * {@inheritDoc} */ @Override - protected String[] searchCols() { - return new String[] { - CustomerProperty.NAME, - CustomerProperty.NOTE - }; + protected List searchCols() { + return SEARCH_COLS; } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/HomePageController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/HomePageController.java index 9ff1977..4754ec4 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/HomePageController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/HomePageController.java @@ -19,6 +19,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import com.pudonghot.ambition.crm.form.create.HomePageFormForCreate; import com.pudonghot.ambition.crm.form.update.HomePageFormForUpdate; +import java.util.Arrays; +import java.util.List; + /** * @author Shaun Chyxion
* chyxion@163.com
@@ -29,10 +32,10 @@ import com.pudonghot.ambition.crm.form.update.HomePageFormForUpdate; @RequestMapping("/home-page") public class HomePageController extends BaseQueryController { - private static final String[] SEARCH_COLS = new String[] { - HomePage.NAME, - HomePage.NOTE - }; + private static final List SEARCH_COLS = + Arrays.asList( + HomePage.NAME, + HomePage.NOTE); @RequestMapping("/list") @RequiresRoles(User.ROLE_ADMIN) @@ -47,7 +50,7 @@ public class HomePageController @RequestParam(value = "search", required = false) final String search) { - return listViewModels(start, limit, search, null); + return listViewModels(start, limit, search); } @RequiresRoles(User.ROLE_ADMIN) @@ -85,7 +88,7 @@ public class HomePageController * {@inheritDoc} */ @Override - protected String[] searchCols() { + protected List searchCols() { return SEARCH_COLS; } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ImportRecordController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ImportRecordController.java index 7747dfb..124ec4b 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ImportRecordController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ImportRecordController.java @@ -1,6 +1,7 @@ package com.pudonghot.ambition.crm.controller; import java.util.List; +import java.util.Arrays; import lombok.extern.slf4j.Slf4j; import me.chyxion.tigon.model.ViewModel; import javax.validation.constraints.Max; @@ -23,6 +24,14 @@ import org.springframework.web.bind.annotation.RequestMapping; public class ImportRecordController extends BaseQueryController { + private final List SEARCH_COLS = + Arrays.asList("r.type", + "r.note", + "u.name", + "u.en_name", + "u.account", + "u.employee_id" ); + @RequestMapping("/list") @RequiresRoles(User.ROLE_ADMIN) public ViewModel>> list( @@ -36,7 +45,7 @@ public class ImportRecordController @RequestParam(value = "search", required = false) final String search) { - return listViewModels(search(start, limit, search, null) + return listViewModels(search(start, limit, search) .table("r") .desc(ImportRecord.DATE_CREATED)); } @@ -45,7 +54,8 @@ public class ImportRecordController * {@inheritDoc} * @return search cols */ - protected String[] searchCols() { - return new String[] { "r.type", "r.note", "u.name", "u.en_name", "u.account", "u.employee_id" }; + @Override + protected List searchCols() { + return SEARCH_COLS; } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/UserController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/UserController.java index b7b50b8..6b52e89 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/UserController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/UserController.java @@ -1,17 +1,20 @@ package com.pudonghot.ambition.crm.controller; import java.util.Map; +import java.util.List; +import java.util.Arrays; import java.util.HashMap; import javax.validation.Valid; +import me.chyxion.tigon.mybatis.Search; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ListResult; +import org.apache.ibatis.annotations.Param; import javax.validation.constraints.NotNull; import com.pudonghot.ambition.crm.model.User; -import org.apache.ibatis.annotations.Param; -import org.hibernate.validator.constraints.NotBlank; import org.springframework.stereotype.Controller; +import org.hibernate.validator.constraints.NotBlank; import com.pudonghot.ambition.crm.service.UserService; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.web.bind.annotation.RequestParam; @@ -31,22 +34,22 @@ import com.pudonghot.ambition.crm.form.update.UserFormForUpdatePassword; public class UserController extends BaseQueryController { - private static final Map SORT_COLS = new HashMap<>(); - private static final String[] SEARCH_COLS = new String[] { + private static final List SEARCH_COLS = Arrays.asList( User.NAME, User.EN_NAME, User.ACCOUNT, User.EMPLOYEE_ID, - User.MOBILE, - User.EMAIL, - User.NOTE - }; + // User.MOBILE, + // User.EMAIL, + User.NOTE); + + private static final Map ORDER_COLS = new HashMap<>(); static { - SORT_COLS.put(User.ACCOUNT, User.ACCOUNT); - SORT_COLS.put("employeeId", User.EMPLOYEE_ID); - SORT_COLS.put(User.MOBILE, User.MOBILE); - SORT_COLS.put("enName", User.EN_NAME); + ORDER_COLS.put(User.ACCOUNT, User.ACCOUNT); + ORDER_COLS.put("employeeId", User.EMPLOYEE_ID); + ORDER_COLS.put(User.MOBILE, User.MOBILE); + ORDER_COLS.put("enName", User.EN_NAME); } @RequestMapping("/list") @@ -61,9 +64,14 @@ public class UserController final int limit, @RequestParam(value = "search", required = false) final String search, - @RequestParam(value = "sorters", required = false) - final String sorters) { - return listViewModels(start, limit, search, sorters); + @RequestParam(value = "orders", required = false) + final String orders) { + return listViewModels(start, limit, search, null, null, orders); + } + + @RequestMapping("/list-for-select") + public List> listForSelect() { + return queryService.listViewModels(new Search().asc(User.EMPLOYEE_ID)); } @RequiresRoles(User.ROLE_ADMIN) @@ -100,7 +108,7 @@ public class UserController * {@inheritDoc} */ @Override - protected String[] searchCols() { + protected List searchCols() { return SEARCH_COLS; } @@ -108,7 +116,7 @@ public class UserController * {@inheritDoc} */ @Override - protected Map sorterCols() { - return SORT_COLS; + protected String orderCol(final String field) { + return ORDER_COLS.get(field); } } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/WeekGoalController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/WeekGoalController.java index 7fbbe97..0c305e9 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/WeekGoalController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/WeekGoalController.java @@ -35,6 +35,12 @@ import com.pudonghot.ambition.crm.form.update.WeekGoalFormForUpdate; public class WeekGoalController extends BaseQueryController { + private static final List SEARCH_COLS = + Arrays.asList("u.name", + "u.en_name", + "u.account", + "u.employee_id"); + @RequestMapping("/list") public ViewModel>> list( @Min(0) @@ -50,7 +56,7 @@ public class WeekGoalController final String strSearch) { final List employeeIds = employeeIdsFilter(filters); return addYearSum(((WeekGoalService) queryService).listJoinUser( - listFilters(search(start, limit, strSearch, null), employeeIds)), employeeIds); + listFilters(search(start, limit, strSearch), employeeIds)), employeeIds); } @RequestMapping("/mine") @@ -116,8 +122,8 @@ public class WeekGoalController * @return search cols */ @Override - protected String[] searchCols() { - return new String[] { "u.name", "u.en_name", "u.account", "u.employee_id" }; + protected List searchCols() { + return SEARCH_COLS; } /** diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java index 2c888cc..b7870f7 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java @@ -4,6 +4,7 @@ import java.util.Date; import java.io.IOException; import java.io.InputStream; import lombok.extern.slf4j.Slf4j; +import org.springframework.util.Assert; import me.chyxion.tigon.model.ViewModel; import org.springframework.stereotype.Service; import com.pudonghot.ambition.file.AmbitionFileApi; @@ -45,12 +46,31 @@ public class ApplicationServiceSupport final Date now = new Date(); application.setDateUpdated(now); - final MultipartFile[] images = form.getImages(); + uploadImages(id, 0, form.getImages(), form.getImageTitles(), form.getCreatedBy()); + mapper.insert(application); + return toViewModel(application); + } + + /** + * {@inheritDoc} + */ + @Override + public ViewModel update(final ApplicationFormForUpdate form) { + final String id = form.getId(); + final Application application = find(id); + Assert.state(application != null, "No application [" + id + "] found"); + final String updatedBy = form.getUpdatedBy(); + Assert.state(form.isAdmin() || application.getOwner().equals(updatedBy), + "No permission to update application"); + + uploadImages(id, imageMapper.nextSort(id), form.getImages(), form.getImageTitles(), updatedBy); + return update(form.copy(application)); + } + + private void uploadImages(final String id, int sort, final MultipartFile[] images, final String[] titles, final String createdBy) { if (images != null) { - final String createdBy = form.getCreatedBy(); - int sort = 0; + final Date now = new Date(); final String imageFolder = imageFolder(id); - final String[] imageTitles = form.getImageTitles(); int i = 0; for (final MultipartFile image : images) { if (!image.isEmpty()) { @@ -61,7 +81,8 @@ public class ApplicationServiceSupport appImage.setId(imageId); appImage.setApplicationId(id); appImage.setSort(sort++); - appImage.setNote(imageTitles[i]); + appImage.setEnabled(true); + appImage.setNote(titles[i]); appImage.setCreatedBy(createdBy); appImage.setDateCreated(now); imageMapper.insert(appImage); @@ -74,16 +95,6 @@ public class ApplicationServiceSupport ++i; } } - mapper.insert(application); - return toViewModel(application); - } - - /** - * {@inheritDoc} - */ - @Override - public ViewModel update(final ApplicationFormForUpdate form) { - return null; } private String imageFolder(final String appId) { diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java index 9165557..db8a6d5 100644 --- a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java @@ -1,6 +1,8 @@ package com.pudonghot.ambition.crm.mapper; import me.chyxion.tigon.mybatis.BaseMapper; +import org.apache.ibatis.annotations.Param; +import org.hibernate.validator.constraints.NotBlank; import com.pudonghot.ambition.crm.model.ApplicationImage; /** @@ -10,4 +12,10 @@ import com.pudonghot.ambition.crm.model.ApplicationImage; */ public interface ApplicationImageMapper extends BaseMapper { + /** + * find next sort + * @param applicationId application id + * @return next sort + */ + int nextSort(@NotBlank @Param("applicationId") String applicationId); } diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml index 6bc8012..d7fa99c 100644 --- a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml @@ -10,4 +10,11 @@ "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + + diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml index fad4c81..97c8802 100644 --- a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml @@ -10,4 +10,22 @@ "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + + diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFormForUpdate.java b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFormForUpdate.java index bf4490f..934f55b 100644 --- a/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFormForUpdate.java +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFormForUpdate.java @@ -21,6 +21,8 @@ import org.springframework.web.multipart.MultipartFile; public class ApplicationFormForUpdate extends FU2 { private static final long serialVersionUID = 1L; + // current user is admin + private boolean admin; @NotBlank @Length(max = 64) private String name; diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java b/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java index 0bf3057..7627298 100644 --- a/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import me.chyxion.tigon.model.M3; import me.chyxion.tigon.mybatis.Table; +import me.chyxion.tigon.mybatis.Transient; /** * @author Donghuang
@@ -25,4 +26,10 @@ public class Application extends M3 { private String name; private String content; private String owner; + + // Transient Props + @Transient + private String images; + @Transient + private String imageTitles; } diff --git a/web/app/components/customer-application/preview-btn.js b/web/app/components/customer-application/preview-btn.js new file mode 100644 index 0000000..34ea5de --- /dev/null +++ b/web/app/components/customer-application/preview-btn.js @@ -0,0 +1,21 @@ +import Component from '@ember/component'; +import { htmlSafe } from '@ember/string'; + +export default Component.extend({ + tagName: '', + didReceiveAttrs() { + const me = this; + me._super(...arguments); + me.set('content', htmlSafe(me.get('model.content'))); + }, + actions: { + show() { + let me = this; + me.set('show', true); + }, + close() { + let me = this; + me.set('show', false); + } + } +}); diff --git a/web/app/components/form-input-select2.js b/web/app/components/form-input-select2.js index 482de1c..b8f7469 100644 --- a/web/app/components/form-input-select2.js +++ b/web/app/components/form-input-select2.js @@ -32,6 +32,7 @@ export default BaseFormInput.extend({ allowClear: me.get('nullable'), placeholder: me.get('placeholder') || me.get('label') || 'Please select...' }).on('change', function(e) { + console.log('select2 change: ', e); if (e.removed) { me.unselect(me.findOption(e.removed.id)); } @@ -64,7 +65,7 @@ export default BaseFormInput.extend({ if (me.get('multiple')) { const vals = me.getVal(); if (isArray(vals)) { - vals.forEach(v => me.select(me.findOption(v))); + vals.each(v => me.select(me.findOption(v))); } else { me.setVal(me.getSelected()); @@ -101,6 +102,6 @@ export default BaseFormInput.extend({ getSelected() { const me = this; const selected = me.get('options').filter(o => o.selected).mapBy(me.get('value-field')); - return me.get('multiple') ? selected : selected[0]; + return selected.length > 1 ? selected : selected.length == 1 ? selected[0] : null; } }); diff --git a/web/app/components/image-previews.js b/web/app/components/image-previews.js index 80d435d..d180e02 100644 --- a/web/app/components/image-previews.js +++ b/web/app/components/image-previews.js @@ -1,18 +1,25 @@ import Ember from 'ember'; +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import $ from 'jquery' -export default Ember.Component.extend({ +export default Component.extend({ tagName: 'span', classNames: ['inline'], + index: 0, + images: computed.alias('previews'), + 'image-height': 22, + imageHeight: computed.alias('image-height'), + 'image-style': 'border-radius: 8%; border: 1px solid #DCDCDC; max-width: 32px;', + imageStyle: computed.alias('image-style'), didReceiveAttrs() { let me = this; - let previews = me.get('previews'); - if (Ember.$.type(previews) === 'string') { + let images = me.get('images'); + if (Ember.$.type(images) === 'string') { let sep = me.get('separator'); - me.set('previews', sep ? previews.split(sep) : [previews]); + me.set('images', sep ? images.split(sep) : [images]); } }, - imageHeight: 22, - imageStyle: 'border-radius: 8%; border: 1px solid #DCDCDC; max-width: 32px;', didInsertElement() { let me = this; me._super(...arguments); diff --git a/web/app/components/modal-dialog.js b/web/app/components/modal-dialog.js index 3792f03..570e080 100644 --- a/web/app/components/modal-dialog.js +++ b/web/app/components/modal-dialog.js @@ -1,16 +1,22 @@ -import Ember from 'ember'; +import Component from '@ember/component' import BaseComponentMixin from '../mixins/components/base-component'; -export default Ember.Component.extend(BaseComponentMixin, { +export default Component.extend(BaseComponentMixin, { classNames: ['modal', 'fade'], 'init-modal': true, - transitionToParentRouteAfterClose: true, + 'close-to-parent': true, + 'cancel-text': 'Cancel', + 'submit-text': 'Submit', + keyboard: true, didInsertElement() { let me = this; me._super(...arguments); if (me.get('init-modal')) { - me.$().modal().on('hidden.bs.modal', ()=> { - me.$() && me.get('transitionToParentRouteAfterClose') && + me.$().modal({ + backdrop: me.get('backdrop'), + keyboard: me.get('keyboard') + }).on('hidden.bs.modal', ()=> { + me.$() && me.get('close-to-parent') && me.get('router').transitionTo( me.get('parentRouteName') || me.get('routeName').replace(/\.[^.]+$/, '')); diff --git a/web/app/components/option-text.js b/web/app/components/option-text.js index 381ef50..2fa9f67 100644 --- a/web/app/components/option-text.js +++ b/web/app/components/option-text.js @@ -10,7 +10,8 @@ const OptionTextComponent = Component.extend({ me._super(...arguments); let val = me.get('value'); if (val) { - const option = (me.get('options') || []).findBy(me.get('value-field'), val); + const valueField = me.get('value-field'); + const option = (me.get('options') || []).findBy(valueField, val); if (option) { let text = option[me.get('text-field')]; let textExp = me.get('text-exp'); diff --git a/web/app/components/sortable-th.js b/web/app/components/sortable-th.js index 43a8c10..b496ade 100644 --- a/web/app/components/sortable-th.js +++ b/web/app/components/sortable-th.js @@ -3,7 +3,7 @@ import BaseComponentMixin from '../mixins/components/base-component'; export default Ember.Component.extend(BaseComponentMixin, { tagName: 'th', - order: Ember.computed('route.controller.sorters', function() { + order: Ember.computed('route.controller.orders', function() { return this.getDir(); }), sorting: Ember.computed.none('order'), @@ -14,44 +14,44 @@ export default Ember.Component.extend(BaseComponentMixin, { getDir() { let me = this; let name = me.get('name'); - let sorters = me.getSorters(); - if (sorters && sorters.length) { - let sorter = sorters.find(sorter => sorter.hasOwnProperty(name)); - if (sorter) { - return sorter[name]; + let orders = me.getOrders(); + if (orders && orders.length) { + let order = orders.find(order => order.hasOwnProperty(name)); + if (order) { + return order[name]; } } }, - getSorters() { + getOrders() { let me = this; - let strSorters = me.get('route.controller.sorters'); - if (strSorters) { - return JSON.parse(strSorters); + let strOrders = me.get('route.controller.orders'); + if (strOrders) { + return JSON.parse(strOrders); } }, actions: { sort() { let me = this; - let sorters = me.getSorters() || []; + let orders = me.getOrders() || []; let name = me.get('name'); - if (sorters.length) { - sorters = sorters.filter(sorter => !sorter.hasOwnProperty(name)); + if (orders.length) { + orders = orders.filter(order => !order.hasOwnProperty(name)); } - let sorter = {}; - sorter[name] = + let order = {}; + order[name] = me.get('order') === 'asc' ? 'desc' : 'asc'; // prepend sort - sorters.unshift(sorter); - me.set('route.controller.sorters', JSON.stringify(sorters)); + orders.unshift(order); + me.set('route.controller.orders', JSON.stringify(orders)); }, removeSort() { let me = this; - let sorters = me.getSorters(); - if (sorters) { + let orders = me.getOrders(); + if (orders) { let name = me.get('name'); - me.set('route.controller.sorters', - JSON.stringify(sorters.filter(sorter => !sorter.hasOwnProperty(name)))); + me.set('route.controller.orders', + JSON.stringify(orders.filter(order => !order.hasOwnProperty(name)))); } } } diff --git a/web/app/helpers/customer-application-images.js b/web/app/helpers/customer-application-images.js new file mode 100644 index 0000000..340c700 --- /dev/null +++ b/web/app/helpers/customer-application-images.js @@ -0,0 +1,16 @@ +import { helper } from '@ember/component/helper'; + +export function customerApplicationImages([fileBasePath, appId, images, titles]) { + if (images) { + titles = titles ? titles.split(String.fromCharCode(0x1d)) : []; + return images.split(String.fromCharCode(0x1d)).map((image, i) => { + return { + image: fileBasePath + 'app/' + appId + '/' + image, + title: titles[i] + } + }); + } + return []; +} + +export default helper(customerApplicationImages); diff --git a/web/app/routes/base-list.js b/web/app/routes/base-list.js index c95f31d..7e9f353 100644 --- a/web/app/routes/base-list.js +++ b/web/app/routes/base-list.js @@ -6,7 +6,10 @@ export default BaseRoute.extend({ search: { refreshModel: true }, - sorters: { + filters: { + refreshModel: true + }, + orders: { refreshModel: true } }, diff --git a/web/app/routes/customer-application/create.js b/web/app/routes/customer-application/create.js index c172484..f3a4d9b 100644 --- a/web/app/routes/customer-application/create.js +++ b/web/app/routes/customer-application/create.js @@ -1,6 +1,7 @@ import Ember from 'ember'; import BaseRoute from '../base'; import EmberObject, { computed } from '@ember/object'; +import RSVP from 'rsvp'; export default BaseRoute.extend({ breadcrumbs: [{route: 'customer-application.list', params: 1, text: 'Customer Application'}, @@ -11,9 +12,10 @@ export default BaseRoute.extend({ }) }), model() { - return this.get('modelClass').create({ + return RSVP.hash({ enabled: true, - images: [{}] + images: [{}], + users: this.get('store').ajaxGet('user/list-for-select') }); }, actions: { diff --git a/web/app/routes/customer-application/edit.js b/web/app/routes/customer-application/edit.js index b656b6e..4b5f5f0 100644 --- a/web/app/routes/customer-application/edit.js +++ b/web/app/routes/customer-application/edit.js @@ -2,10 +2,23 @@ import Ember from 'ember'; import BaseEditRoute from '../base-edit'; export default BaseEditRoute.extend({ - service: Ember.inject.service('customer-property.service'), afterModel(model) { + const me = this; + me._super(...arguments); + model.images = [{}]; this.set('breadcrumbs', [{route: 'customer-application.list', params: 1, text: 'Customer Application'}, {text: 'Edit Customer Application[' + model.name + ']'}]); + }, + + actions: { + addImage() { + const me = this; + me.get('controller.model.images').pushObject({}); + }, + removeImage(image) { + const me = this; + me.get('controller.model.images').removeObject(image); + }, } }); diff --git a/web/app/routes/customer-application/list.js b/web/app/routes/customer-application/list.js index 18c105f..e3d977c 100644 --- a/web/app/routes/customer-application/list.js +++ b/web/app/routes/customer-application/list.js @@ -6,4 +6,18 @@ export default BaseListRoute.extend({ // model(params) { // return this.get('store').ajaxGet('customer-property/app-list', params); // } + actions: { + showContent(app) { + this.get('dialog').dialog({ + title: app.name, + message: `

${app.content}

`, + buttons: { + ok: { + label: 'OK', + className: 'btn-info' + } + } + }); + } + } }); diff --git a/web/app/routes/customer/list.js b/web/app/routes/customer/list.js index 8a250a5..be0f553 100644 --- a/web/app/routes/customer/list.js +++ b/web/app/routes/customer/list.js @@ -65,9 +65,6 @@ export default BaseListRoute.extend({ queryParams: { criteria: { refreshModel: true - }, - filters: { - refreshModel: true } }, breadcrumbs: [{text: 'Customers'}], diff --git a/web/app/templates/components/customer-application/preview-btn.hbs b/web/app/templates/components/customer-application/preview-btn.hbs new file mode 100644 index 0000000..22c74d7 --- /dev/null +++ b/web/app/templates/components/customer-application/preview-btn.hbs @@ -0,0 +1,18 @@ + + +{{#if show}} + {{#modal-dialog title=model.name no-cancel=true submit=(action 'close') on-close=(action 'close') close-to-parent=false}} +
+
+

{{content}}

+
+ {{#with (customer-application-images file-base-path model.id model.images model.imageTitles) as |images|}} + {{#each images as |image index|}} + {{image-previews cover-title=true image-height=192 image-style='border-radius: 2%; border: 1px solid #DCDCDC; max-width: 480px;' previews=images index=index}} + {{/each}} + {{/with}} +
+ {{/modal-dialog}} +{{/if}} diff --git a/web/app/templates/components/image-previews.hbs b/web/app/templates/components/image-previews.hbs index 72ce668..3ddef2f 100644 --- a/web/app/templates/components/image-previews.hbs +++ b/web/app/templates/components/image-previews.hbs @@ -1,12 +1,23 @@ -{{#if previews}} - - - - + {{/if}} \ No newline at end of file diff --git a/web/app/templates/components/modal-dialog.hbs b/web/app/templates/components/modal-dialog.hbs index a5fd11b..9a61f30 100644 --- a/web/app/templates/components/modal-dialog.hbs +++ b/web/app/templates/components/modal-dialog.hbs @@ -1,25 +1,33 @@ diff --git a/web/app/templates/components/th-filter.hbs b/web/app/templates/components/th-filter.hbs index 9eaaa4f..d79cd75 100644 --- a/web/app/templates/components/th-filter.hbs +++ b/web/app/templates/components/th-filter.hbs @@ -11,8 +11,9 @@ col-width=12 label=(if label label text) options=options - value-field=(get this 'value-field') - text-field=(get this 'text-field') + value-field=value-field + text-field=text-field + text-exp=text-exp }} diff --git a/web/app/templates/customer-application/create.hbs b/web/app/templates/customer-application/create.hbs index ddbc999..9a4e3dc 100644 --- a/web/app/templates/customer-application/create.hbs +++ b/web/app/templates/customer-application/create.hbs @@ -1,5 +1,4 @@ {{#form-content}} - {{form-input type='hidden' name='type'}} {{form-input name='name' label='Name'}} {{#form-input name='content' label='Content'}} {{wysiwyg-editor model=model name='content'}} @@ -16,7 +15,6 @@
- {{#each model.images as |image i|}}
@@ -29,12 +27,20 @@ {{image-input name=(concat 'images[' i ']') image=image}}
{{/each}} -
{{/form-input}} {{form-input name='note' label='Remark'}} + {{form-input-select2 + nullable=true + name='owner' + label='Owner' + options=model.users + value-field='id' + text-field='name' + text-exp='$.employeeId ($.name)' + }}
{{form-footer-buttons}} {{/form-content}} diff --git a/web/app/templates/customer-application/edit.hbs b/web/app/templates/customer-application/edit.hbs index c2846dc..2f06e43 100644 --- a/web/app/templates/customer-application/edit.hbs +++ b/web/app/templates/customer-application/edit.hbs @@ -1,7 +1,39 @@ {{#form-content}} {{input type='hidden' name='id' value=model.id}} - {{form-input type='hidden' name='type'}} {{form-input name='name' label='Name'}} + {{#form-input name='content' label='Content'}} + {{wysiwyg-editor model=model name='content'}} + {{/form-input}} + + {{#form-input name='image' label='Images'}} +
+
+
+ + + +
+
+
+
+ {{#each model.images as |image i|}} +
+
+ {{input name=(concat 'imageTitles[' i ']') class='width-80' placeholder='Image title' value=image.title}} +   + + + +
+ {{image-input name=(concat 'images[' i ']') image=image}} +
+ {{/each}} +
+
+
+ {{/form-input}} + + {{form-input-enabled label='Enabled' enabledText='TRUE' disabledText='FALSE'}} {{form-input name='note' label='Remark'}}
diff --git a/web/app/templates/customer-application/list.hbs b/web/app/templates/customer-application/list.hbs index 6244002..1b6eb39 100644 --- a/web/app/templates/customer-application/list.hbs +++ b/web/app/templates/customer-application/list.hbs @@ -22,20 +22,33 @@ Name + + Content + Remark + {{#if ajax.user.admin}} + + + {{th-filter name='owner' + text='Owner' + label='Owner Filter' + options=model.users + value-field='id' + text-exp='$.employeeId ($.name)' + }} + + {{/if}} Enabled - {{#if ajax.user.admin}} Settings - {{/if}} @@ -46,16 +59,20 @@ {{it.name}} - {{#if ajax.user.admin}} - {{editable-cell model=it field='note'}} - {{else}} - {{it.note}} - {{/if}} + {{image-previews previews=(customer-application-images model.fileBasePath it.id it.images it.imageTitles)}} + {{customer-application/preview-btn model=it file-base-path=model.fileBasePath}} + + {{editable-cell model=it field='note'}} + + {{#if ajax.user.admin}} + + {{option-text model.users it.owner 'id' 'name' '$.employeeId ($.name)'}} + + {{/if}} {{status-cell model=it enabledText='TRUE' disabledText='FALSE'}} - {{#if ajax.user.admin}} - {{/if}} {{/each}} diff --git a/web/tests/integration/components/customer-application/preview-btn-test.js b/web/tests/integration/components/customer-application/preview-btn-test.js new file mode 100644 index 0000000..dea4645 --- /dev/null +++ b/web/tests/integration/components/customer-application/preview-btn-test.js @@ -0,0 +1,24 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('customer-application/preview-btn', 'Integration | Component | customer application/preview btn', { + integration: true +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{customer-application/preview-btn}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage: + this.render(hbs` + {{#customer-application/preview-btn}} + template block text + {{/customer-application/preview-btn}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/web/tests/integration/helpers/customer-application-images-test.js b/web/tests/integration/helpers/customer-application-images-test.js new file mode 100644 index 0000000..cd39c51 --- /dev/null +++ b/web/tests/integration/helpers/customer-application-images-test.js @@ -0,0 +1,17 @@ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('customer-application-images', 'helper:customer-application-images', { + integration: true +}); + +// Replace this with your real tests. +test('it renders', function(assert) { + this.set('inputValue', '1234'); + + this.render(hbs`{{customer-application-images inputValue}}`); + + assert.equal(this.$().text().trim(), '1234'); +}); +