From 20e191e73a4d3f4b202b1e4c6fdbe4eb1c2a66c1 Mon Sep 17 00:00:00 2001 From: Shaun Chyxion Date: Sat, 24 Nov 2018 17:03:51 +0800 Subject: [PATCH] add local product --- web/app/helpers/application/admin-or-owner.js | 8 + web/app/helpers/local-product/has-perm.js | 7 + web/app/router.js | 6 + web/app/routes/customer-application/create.js | 1 - web/app/routes/customer-application/edit.js | 1 - web/app/routes/customer-application/list.js | 15 -- web/app/routes/local-product/create.js | 34 ++++ web/app/routes/local-product/edit.js | 122 ++++++++++++ web/app/routes/local-product/list.js | 5 + .../services/customer-application/service.js | 1 - web/app/services/local-product/service.js | 15 ++ .../templates/components/main-container.hbs | 6 + .../templates/customer-application/list.hbs | 18 +- web/app/templates/local-product/create.hbs | 64 ++++++ web/app/templates/local-product/edit.hbs | 185 ++++++++++++++++++ web/app/templates/local-product/list.hbs | 109 +++++++++++ .../application/admin-or-owner-test.js | 17 ++ .../helpers/local-product/has-perm-test.js | 17 ++ .../unit/routes/local-product/create-test.js | 11 ++ .../unit/routes/local-product/edit-test.js | 11 ++ .../unit/routes/local-product/list-test.js | 11 ++ .../services/local-product/service-test.js | 12 ++ 22 files changed, 657 insertions(+), 19 deletions(-) create mode 100644 web/app/helpers/application/admin-or-owner.js create mode 100644 web/app/helpers/local-product/has-perm.js create mode 100644 web/app/routes/local-product/create.js create mode 100644 web/app/routes/local-product/edit.js create mode 100644 web/app/routes/local-product/list.js create mode 100644 web/app/services/local-product/service.js create mode 100644 web/app/templates/local-product/create.hbs create mode 100644 web/app/templates/local-product/edit.hbs create mode 100644 web/app/templates/local-product/list.hbs create mode 100644 web/tests/integration/helpers/application/admin-or-owner-test.js create mode 100644 web/tests/integration/helpers/local-product/has-perm-test.js create mode 100644 web/tests/unit/routes/local-product/create-test.js create mode 100644 web/tests/unit/routes/local-product/edit-test.js create mode 100644 web/tests/unit/routes/local-product/list-test.js create mode 100644 web/tests/unit/services/local-product/service-test.js diff --git a/web/app/helpers/application/admin-or-owner.js b/web/app/helpers/application/admin-or-owner.js new file mode 100644 index 0000000..4d2f4c2 --- /dev/null +++ b/web/app/helpers/application/admin-or-owner.js @@ -0,0 +1,8 @@ +import { helper } from '@ember/component/helper'; + +export function applicationAdminOrOwner(params/*, hash*/) { + const user = params[1]; + return user.admin || params[0].filterBy('owner', user.id).length; +} + +export default helper(applicationAdminOrOwner); diff --git a/web/app/helpers/local-product/has-perm.js b/web/app/helpers/local-product/has-perm.js new file mode 100644 index 0000000..40811a6 --- /dev/null +++ b/web/app/helpers/local-product/has-perm.js @@ -0,0 +1,7 @@ +import { helper } from '@ember/component/helper'; + +export function localProductHasPerm(params/*, hash*/) { + return ['leli', 'chyxion'].includes(params[0].account); +} + +export default helper(localProductHasPerm); diff --git a/web/app/router.js b/web/app/router.js index 2d8e8d4..d6a53e2 100644 --- a/web/app/router.js +++ b/web/app/router.js @@ -74,6 +74,12 @@ Router.map(function() { this.route('create'); this.route('edit', {path: '/:id/edit'}); }); + + this.route('local-product', function() { + this.route('list', {path: '/list/:page'}); + this.route('create'); + this.route('edit', {path: '/:id/edit'}); + }); }); export default Router; diff --git a/web/app/routes/customer-application/create.js b/web/app/routes/customer-application/create.js index d7c75f7..19603ea 100644 --- a/web/app/routes/customer-application/create.js +++ b/web/app/routes/customer-application/create.js @@ -1,4 +1,3 @@ -import Ember from 'ember'; import BaseRoute from '../base'; import EmberObject, { computed } from '@ember/object'; import RSVP from 'rsvp'; diff --git a/web/app/routes/customer-application/edit.js b/web/app/routes/customer-application/edit.js index bd222b6..b670e01 100644 --- a/web/app/routes/customer-application/edit.js +++ b/web/app/routes/customer-application/edit.js @@ -1,4 +1,3 @@ -import Ember from 'ember'; import BaseEditRoute from '../base-edit'; import $ from 'jquery'; diff --git a/web/app/routes/customer-application/list.js b/web/app/routes/customer-application/list.js index a1fdf4f..79d994b 100644 --- a/web/app/routes/customer-application/list.js +++ b/web/app/routes/customer-application/list.js @@ -1,20 +1,5 @@ -import Ember from 'ember'; import BaseListRoute from './../base-list'; export default BaseListRoute.extend({ breadcrumbs: [{text: 'Customer Application'}], - 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/local-product/create.js b/web/app/routes/local-product/create.js new file mode 100644 index 0000000..84d2fd5 --- /dev/null +++ b/web/app/routes/local-product/create.js @@ -0,0 +1,34 @@ +import BaseRoute from '../base'; +import EmberObject, { computed } from '@ember/object'; +import RSVP from 'rsvp'; + +export default BaseRoute.extend({ + breadcrumbs: [{route: 'local-product.list', params: 1, text: 'Local Product'}, + {text: 'Create Local Product'}], + modelClass: EmberObject.extend({ + hasImage: computed('images.@each.file', function() { + return this.get('images').filter(image => image.file).length > 0; + }) + }), + model() { + return RSVP.hash({ + enabled: true, + images: [{}], + attachments: [{}] + }); + }, + actions: { + addImage() { + this.get('controller.model.images').pushObject({}); + }, + removeImage(image) { + this.get('controller.model.images').removeObject(image); + }, + addAttachment() { + this.get('controller.model.attachments').pushObject({}); + }, + removeAttachment(attachment) { + this.get('controller.model.attachments').removeObject(attachment); + } + } +}); diff --git a/web/app/routes/local-product/edit.js b/web/app/routes/local-product/edit.js new file mode 100644 index 0000000..24e5b8f --- /dev/null +++ b/web/app/routes/local-product/edit.js @@ -0,0 +1,122 @@ +import BaseEditRoute from '../base-edit'; +import $ from 'jquery'; + +export default BaseEditRoute.extend({ + afterModel(model) { + const me = this; + me._super(...arguments); + this.set('breadcrumbs', + [{route: 'local-product.list', params: 1, text: 'Local Product'}, + {text: 'Edit Local Product [' + model.name + ']'}]); + }, + actions: { + addImage() { + const me = this; + me.set('controller.model.addImage', true); + me.set('controller.model.image', {}); + }, + submitAddImage() { + const me = this; + me.set('controller.errors', {}); + if (me.get('controller.model.image.file')) { + me.set('controller.errors', {}); + me.get('store').ajaxPost('local-product/add-image', + new FormData($('#form_' + me.get('controller.model.id'))[0])) + .then(image => { + me.get('controller.model.images').pushObject(image); + me.set('controller.model.addImage', false); + }); + } + else { + me.set('controller.errors.image', ['No image file selected']); + } + }, + closeAddImage() { + const me = this; + me.set('controller.model.addImage', false); + }, + removeImage(image) { + const me = this; + me.get('dialog').confirm('Are you sure to remove image?', () => { + me.get('store').ajaxPost('local-product/remove-image', image).then(() => { + me.get('message').alert('Image removed'); + me.get('controller.model.images').removeObject(image); + }); + }); + }, + moveImageUp(image) { + const me = this; + me.moveUp(me.get('controller.model.images'), image, 'updateImage'); + }, + moveImageDown(image) { + const me = this; + me.moveDown(me.get('controller.model.images'), image, 'updateImage'); + }, + // attachments + addAttachment() { + const me = this; + me.set('controller.model.addAttachment', true); + me.set('controller.model.attachment', {}); + }, + submitAddAttachment() { + const me = this; + me.set('controller.errors', {}); + if (me.get('controller.model.attachment.file')) { + me.get('store').ajaxPost('local-product/add-attachment', + new FormData($('#form_' + me.get('controller.model.id'))[0])) + .then(attachment => { + me.get('controller.model.attachments').pushObject(attachment); + me.set('controller.model.addAttachment', false); + }); + } + else { + me.set('controller.errors.attachment', ['No attachment file selected']); + } + }, + closeAddAttachment() { + const me = this; + me.set('controller.model.addAttachment', false); + }, + removeAttachment(attachment) { + const me = this; + me.get('dialog').confirm('Are you sure to remove attachment?', () => { + me.get('store').ajaxPost('local-product/remove-attachment', attachment).then(() => { + me.get('message').alert('Attachment removed'); + me.get('controller.model.attachments').removeObject(attachment); + }); + }); + }, + moveAttachmentUp(attachment) { + const me = this; + me.moveUp(me.get('controller.model.attachments'), attachment, 'updateAttachment'); + }, + moveAttachmentDown(attachment) { + const me = this; + me.moveDown(me.get('controller.model.attachments'), attachment, 'updateAttachment'); + } + }, + updateImage(image) { + this.get('ajax').doPost('local-product/update-image', image, false); + }, + updateAttachment(attachment) { + this.get('ajax').doPost('local-product/update-attachment', attachment, false); + }, + moveUp(files, file, update) { + if (files && files.length > 1) { + let index = files.indexOf(file); + files.removeObject(file); + files.insertAt(index - 1, file); + --file.sort; + this[update](file); + } + }, + moveDown(files, file, update) { + if (files && files.length > 1) { + let index = files.indexOf(file); + files.removeObject(file); + files.insertAt(index + 1, file); + ++file.sort; + this[update](file); + } + } +}); diff --git a/web/app/routes/local-product/list.js b/web/app/routes/local-product/list.js new file mode 100644 index 0000000..80bc694 --- /dev/null +++ b/web/app/routes/local-product/list.js @@ -0,0 +1,5 @@ +import BaseListRoute from './../base-list'; + +export default BaseListRoute.extend({ + breadcrumbs: [{text: 'Local Product'}] +}); diff --git a/web/app/services/customer-application/service.js b/web/app/services/customer-application/service.js index b0149a1..6c4665b 100644 --- a/web/app/services/customer-application/service.js +++ b/web/app/services/customer-application/service.js @@ -1,4 +1,3 @@ -import Ember from 'ember'; import BaseService from '../service'; export default BaseService.extend({ diff --git a/web/app/services/local-product/service.js b/web/app/services/local-product/service.js new file mode 100644 index 0000000..754a6be --- /dev/null +++ b/web/app/services/local-product/service.js @@ -0,0 +1,15 @@ +import BaseService from '../service'; + +export default BaseService.extend({ + pageSize: 128, + modelName: 'LocalProduct', + constraints: { + name: { + presence: true, + length: { + minimum: 1, + maximum: 128 + } + } + } +}); diff --git a/web/app/templates/components/main-container.hbs b/web/app/templates/components/main-container.hbs index be27b56..95491cf 100644 --- a/web/app/templates/components/main-container.hbs +++ b/web/app/templates/components/main-container.hbs @@ -71,6 +71,12 @@ Customer Application {{/link-to}} +
  • + {{#link-to 'local-product.list' 1}} + + Local Product + {{/link-to}} +
  • {{#if ajax.user.admin}}
  • {{#link-to 'import-record.list' 1}} diff --git a/web/app/templates/customer-application/list.hbs b/web/app/templates/customer-application/list.hbs index f53246c..f2435ff 100644 --- a/web/app/templates/customer-application/list.hbs +++ b/web/app/templates/customer-application/list.hbs @@ -32,6 +32,7 @@ Remark + {{#if ajax.user.admin}} {{th-filter name='owner' @@ -42,6 +43,8 @@ text-exp='$.employeeId ($.name)' }} + {{/if}} + {{#if (application/admin-or-owner model.data ajax.user)}} Enabled @@ -50,23 +53,34 @@ Settings + {{/if}} {{#each model.data as |it|}} + {{#if (or it.enabled (or ajax.user.admin (eq ajax.user.id it.owner)))}} {{customer-application/preview-btn model=it}} - {{editable-cell model=it field='note'}} + {{#if (or ajax.user.admin (eq ajax.user.id it.owner))}} + {{editable-cell model=it field='note'}} + {{else}} + {{it.note}} + {{/if}} + {{#if ajax.user.admin}} {{option-text model.users it.owner 'id' 'name' '$.employeeId ($.name)'}} + {{/if}} + {{#if (application/admin-or-owner model.data ajax.user)}} + {{#if (or ajax.user.admin (eq ajax.user.id it.owner))}} {{status-cell model=it enabledText='TRUE' disabledText='FALSE'}} + {{/if}} + {{/if}} + {{/if}} {{/each}} diff --git a/web/app/templates/local-product/create.hbs b/web/app/templates/local-product/create.hbs new file mode 100644 index 0000000..d369c06 --- /dev/null +++ b/web/app/templates/local-product/create.hbs @@ -0,0 +1,64 @@ +{{#form-content}} + {{form-input name='name' label='Name'}} + {{form-input name='note' label='Remark'}} + {{form-input name='content' label='Content' type='textarea'}} + + {{#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 name='attachment' label='Attachments'}} +
    +
    +
    + + + +
    +
    +
    +
    + {{#each model.attachments as |attachment i|}} +
    +
    + {{input name=(concat 'attachmentTitles[' i ']') class='width-80' placeholder='Attachment name' value=image.title}} +   + + + +
    + {{!input type='file'}} + {{file-input name=(concat 'attachments[' i ']') file=attachment}} +
    + {{/each}} +
    +
    +
    + {{/form-input}} +
    + {{form-footer-buttons}} +{{/form-content}} diff --git a/web/app/templates/local-product/edit.hbs b/web/app/templates/local-product/edit.hbs new file mode 100644 index 0000000..04c227e --- /dev/null +++ b/web/app/templates/local-product/edit.hbs @@ -0,0 +1,185 @@ +{{#form-content}} + {{input type='hidden' name='id' value=model.id}} + {{form-input name='name' label='Name'}} + {{form-input name='note' label='Remark'}} + + {{form-input name='content' label='Content' type='textarea'}} + {{!-- {{#form-input name='content' label='Content'}} + {{wysiwyg-editor model=model name='content'}} + {{/form-input}} --}} + + {{#form-input label='Images' name='images'}} +
    + +
    + {{!--
    + + Users +
    --}} +
    + + + +
    +
    + + +
    +
    + + + + + + + + + + + {{#each model.images as |image|}} + + + + + + {{/each}} + +
    + + Image + + + Description + + + Settings +
    + {{image-previews images=image.url}} + + {{editable-cell model=image field='note' post-url='local-product/update-image'}} + + {{!image.note}} +
    + + + + {{#if (not-eq model.images.firstObject.id image.id)}} + + + + {{/if}} + {{#if (not-eq model.images.lastObject.id image.id)}} + + + + {{/if}} +
    +
    +
    +
    +
    + {{/form-input}} + + {{#form-input label='Attachments' name='attachments'}} +
    + +
    +
    + + + +
    +
    + + +
    +
    + + + + + + + + + + + {{#each model.attachments as |attachment|}} + + + + + + {{/each}} + +
    + + Attachment + + + Description + + + Settings +
    + {{!image-previews images=image.url}} + + + Download + + + {{editable-cell model=attachment field='note' post-url='local-product/update-attachment'}} + +
    + + + + {{#if (not-eq model.images.firstObject.id attachment.id)}} + + + + {{/if}} + {{#if (not-eq model.images.lastObject.id attachment.id)}} + + + + {{/if}} +
    +
    +
    +
    +
    + {{/form-input}} + + {{form-input-enabled label='Enabled' enabledText='TRUE' disabledText='FALSE'}} +
    + {{form-footer-buttons type='update'}} +{{/form-content}} + +{{#if model.addImage}} + {{#modal-dialog title='Add Image' no-cancel=true submit=(route-action 'submitAddImage') on-close=(route-action 'closeAddImage') close-to-parent=false}} + {{#form-content form-id=(concat 'form_' model.id)}} + {{input type='hidden' name='productId' value=model.id}} + {{#form-input name='note' label='Description'}} + {{input name='note' value=model.image.note class='col-xs-12'}} + {{/form-input}} + {{#form-input name='image' label='Image'}} + {{image-input name='image' image=model.image}} + {{/form-input}} + {{/form-content}} + {{/modal-dialog}} +{{/if}} + +{{#if model.addAttachment}} + {{#modal-dialog title='Add Attachment' no-cancel=true submit=(route-action 'submitAddAttachment') on-close=(route-action 'closeAddAttachment') close-to-parent=false}} + {{#form-content form-id=(concat 'form_' model.id)}} + {{input type='hidden' name='productId' value=model.id}} + {{#form-input name='note' label='Description'}} + {{input name='note' value=model.attachment.note class='col-xs-12'}} + {{/form-input}} + {{#form-input name='attachment' label='Attachment'}} + {{file-input name='attachment' file=model.attachment}} + {{/form-input}} + {{/form-content}} + {{/modal-dialog}} +{{/if}} diff --git a/web/app/templates/local-product/list.hbs b/web/app/templates/local-product/list.hbs new file mode 100644 index 0000000..0ed6723 --- /dev/null +++ b/web/app/templates/local-product/list.hbs @@ -0,0 +1,109 @@ +{{#main-content}} +
    + {{#if (local-product/has-perm ajax.user)}} + {{#grid-header}} +
  • + {{#link-to 'local-product.create'}} + + Create Local Product + {{/link-to}} +
  • + {{/grid-header}} + {{else}} + {{grid-header}} + {{/if}} + +
    + +
    + + + + + + {{#if (local-product/has-perm ajax.user)}} + + + {{/if}} + + + + + {{#each model.data as |it|}} + + + + {{#if (local-product/has-perm ajax.user)}} + + + {{/if}} + + {{/each}} + +
    + {{th-filter name='namePrefix' + text='Name' + label='Name Filter' + options=model.namePrefixes + value-field='value' + text-field='text' + }} + + + Remark + + + Enabled + + + Settings +
    + {{customer-application/preview-btn model=it}} + + {{#if (local-product/has-perm ajax.user)}} + {{editable-cell model=it field='note'}} + {{else}} + {{it.note}} + {{/if}} + + {{status-cell model=it enabledText='TRUE' disabledText='FALSE'}} + + + +
    +
    + {{pagination-bar}} +
    + +{{/main-content}} diff --git a/web/tests/integration/helpers/application/admin-or-owner-test.js b/web/tests/integration/helpers/application/admin-or-owner-test.js new file mode 100644 index 0000000..b24ade6 --- /dev/null +++ b/web/tests/integration/helpers/application/admin-or-owner-test.js @@ -0,0 +1,17 @@ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('application/admin-or-owner', 'helper:application/admin-or-owner', { + integration: true +}); + +// Replace this with your real tests. +test('it renders', function(assert) { + this.set('inputValue', '1234'); + + this.render(hbs`{{application/admin-or-owner inputValue}}`); + + assert.equal(this.$().text().trim(), '1234'); +}); + diff --git a/web/tests/integration/helpers/local-product/has-perm-test.js b/web/tests/integration/helpers/local-product/has-perm-test.js new file mode 100644 index 0000000..af38ed1 --- /dev/null +++ b/web/tests/integration/helpers/local-product/has-perm-test.js @@ -0,0 +1,17 @@ + +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('local-product/has-perm', 'helper:local-product/has-perm', { + integration: true +}); + +// Replace this with your real tests. +test('it renders', function(assert) { + this.set('inputValue', '1234'); + + this.render(hbs`{{local-product/has-perm inputValue}}`); + + assert.equal(this.$().text().trim(), '1234'); +}); + diff --git a/web/tests/unit/routes/local-product/create-test.js b/web/tests/unit/routes/local-product/create-test.js new file mode 100644 index 0000000..ddb6f39 --- /dev/null +++ b/web/tests/unit/routes/local-product/create-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:local-product/create', 'Unit | Route | local product/create', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/web/tests/unit/routes/local-product/edit-test.js b/web/tests/unit/routes/local-product/edit-test.js new file mode 100644 index 0000000..affb44e --- /dev/null +++ b/web/tests/unit/routes/local-product/edit-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:local-product/edit', 'Unit | Route | local product/edit', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/web/tests/unit/routes/local-product/list-test.js b/web/tests/unit/routes/local-product/list-test.js new file mode 100644 index 0000000..8a1b16f --- /dev/null +++ b/web/tests/unit/routes/local-product/list-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:local-product/list', 'Unit | Route | local product/list', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/web/tests/unit/services/local-product/service-test.js b/web/tests/unit/services/local-product/service-test.js new file mode 100644 index 0000000..cea703e --- /dev/null +++ b/web/tests/unit/services/local-product/service-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('service:local-product/service', 'Unit | Service | local product/service', { + // Specify the other units that are required for this test. + // needs: ['service:foo'] +}); + +// Replace this with your real tests. +test('it exists', function(assert) { + let service = this.subject(); + assert.ok(service); +});