diff --git a/app/components/progress-bar.hbs b/app/components/progress-bar.hbs new file mode 100644 index 00000000..e9220e79 --- /dev/null +++ b/app/components/progress-bar.hbs @@ -0,0 +1,51 @@ +
+ + + +
\ No newline at end of file diff --git a/app/components/progress-bar.js b/app/components/progress-bar.js new file mode 100644 index 00000000..6aea892e --- /dev/null +++ b/app/components/progress-bar.js @@ -0,0 +1,47 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { debounce } from '@ember/runloop'; + +export default class ProgressBarComponent extends Component { + @tracked isEditable = false; + @tracked value = this.args.value; + lastEditTime = null; + + @action turnEditModeOn() { + this.isEditable = true; + this.lastEditTime = Date.now(); + this.setEditableToFalse(); + } + + setEditableToFalse() { + setTimeout(() => { + const timeDelta = Date.now() - this.lastEditTime; + if (this.isEditable && timeDelta >= 5000) { + this.isEditable = false; + } else if (this.isEditable) { + this.setEditableToFalse(); + } + }, 5000); + } + + @action onInput(e) { + this.lastEditTime = Date.now(); + this.value = e.target.value; + if (this.args.onInput) { + this.args.onInput(this.value); + } + } + + @action onChange(e) { + this.lastEditTime = Date.now(); + if (this.args.onChange) { + debounce(this, this.debouncedChange, e, 600); + } + } + + async debouncedChange(e) { + await this.args.onChange(e); + this.isEditable = false; + } +} diff --git a/app/components/task-details.hbs b/app/components/task-details.hbs index 640e17f4..cc3c345d 100644 --- a/app/components/task-details.hbs +++ b/app/components/task-details.hbs @@ -2,22 +2,38 @@

{{@task.title}}

-
- - {{@percentCompleted}}% +
+ {{#if @dev}} + {{#if (eq @task.status 'IN_PROGRESS')}} + + {{/if}} + {{else}} + + {{@percentCompleted}}% + {{/if}} +
diff --git a/app/components/task/holder.hbs b/app/components/task/holder.hbs index 2b96de6f..27beaa29 100644 --- a/app/components/task/holder.hbs +++ b/app/components/task/holder.hbs @@ -7,6 +7,8 @@ @percentCompleted={{this.percentCompleted}} @progressBarClass={{this.progressBarClass}} @isProgressBarDisabled={{this.isProgressBarDisabled}} + @onInputChange={{this.progressBarInputChange}} + @dev={{@dev}} />
diff --git a/app/components/task/holder.js b/app/components/task/holder.js index 1153550a..bf4642c4 100644 --- a/app/components/task/holder.js +++ b/app/components/task/holder.js @@ -66,18 +66,23 @@ export default class TasksHolderComponent extends Component { return 'progress-bar-yellow'; } + @action progressBarInputChange(value) { + this.percentCompleted = value; + } + get isProgressBarDisabled() { return this.args.task.status !== TASK_KEYS.IN_PROGRESS; } @action - onPercentageChange(e) { + async onPercentageChange(e) { const { value } = e.target; this.percentCompleted = value; this.args.onTaskChange('percentCompleted', value); if (value === TASK_PERCENTAGE.completedPercentage) { this.percentCompleted = this.args.task.percentCompleted; } + await this.onUpdate(this.args.task.id); } @action diff --git a/app/components/tasks.hbs b/app/components/tasks.hbs index 1ee3fd20..f7329a3b 100644 --- a/app/components/tasks.hbs +++ b/app/components/tasks.hbs @@ -23,6 +23,7 @@ @onTaskUpdate={{@onTaskUpdate}} @userSelectedTask={{@userSelectedTask}} @disabled={{@disabled}} + @dev={{@dev}} />
{{/each}} diff --git a/app/modifiers/focus-when.js b/app/modifiers/focus-when.js new file mode 100644 index 00000000..fa90e385 --- /dev/null +++ b/app/modifiers/focus-when.js @@ -0,0 +1,7 @@ +import { modifier } from 'ember-modifier'; + +export default modifier(function focusWhen(element, [isFocused]) { + if (isFocused) { + element.focus(); + } +}); diff --git a/app/styles/app.css b/app/styles/app.css index 307b7444..4f75d41c 100644 --- a/app/styles/app.css +++ b/app/styles/app.css @@ -17,6 +17,7 @@ @import 'card.css'; @import 'discord.css'; @import 'qrcode.css'; +@import 'progress-bar.css'; html, body { diff --git a/app/styles/progress-bar.css b/app/styles/progress-bar.css new file mode 100644 index 00000000..0990f110 --- /dev/null +++ b/app/styles/progress-bar.css @@ -0,0 +1,81 @@ +:root { + --progress-bar-color: rgba(0, 128, 0, 0.9); + --light-grey: rgba(128, 128, 128, 0.5); + --black: black; + --white: white; +} +.progress-slider { + display: flex; + gap: 4px; +} + +.progress-slider__button { + width: 1.5rem; + height: 1.1rem; + border: 2px solid var(--black); + border-radius: 4px; + background-color: transparent; + display: flex; + align-items: center; + justify-items: center; +} + +.progress-slider__button__text { + font-size: 0.6em; + font-weight: bolder; + margin: auto; +} + +.progress-slider__button__edit-icon { + width: 1rem; + height: 1rem; + margin: 3px; +} +.progress-slider__input-slider { + -webkit-appearance: none; + appearance: none; + width: 90%; + height: 1.1rem; + cursor: pointer; + outline: none; + overflow: hidden; + border: 2px solid var(--black); + border-radius: 4px; +} + +.progress-slider__input-slider:focus { + box-shadow: 0 0 0 2px var(--progress-bar-color); +} +.progress-slider__input-slider:disabled { + cursor: auto; +} + +.progress-slider__input-slider::-webkit-slider-thumb { + -webkit-appearance: none; + width: 0; + box-shadow: -20rem 0 0 20rem var(--progress-bar-color); +} + +.progress-slider__input-slider::-moz-range-thumb { + border: none; + width: 0; + box-shadow: -20rem 0 0 20rem var(--progress-bar-color); +} + +.progress-slider__input-slider[step] { + background-color: transparent; + background: repeating-linear-gradient( + to right, + var(--white), + var(--white) calc(10.05% - 1.5px), + var(--black) 10.05% + ); +} + +.progress-slider__button--hover:hover { + background-color: var(--light-grey); +} + +.progress-slider__button__edit-icon__inner { + fill: var(--progress-bar-color); +} diff --git a/app/styles/tasks.css b/app/styles/tasks.css index 016cd280..7fa599d1 100644 --- a/app/styles/tasks.css +++ b/app/styles/tasks.css @@ -18,17 +18,17 @@ } .progress-bar-red { - --progress-bar-color: red; + --progress-bar-color:rgba(255, 0, 0, 0.8); } .progress-bar-yellow { - --progress-bar-color: #ecef08; + --progress-bar-color: rgba(255, 255, 0, 0.9); } .progress-bar-green { - --progress-bar-color: green; + --progress-bar-color: rgba(0, 128, 0, 0.9); } .progress-bar-orange { - --progress-bar-color: orange; + --progress-bar-color: rgba(255, 165, 0,0.9); } .tasks-page { @@ -83,8 +83,9 @@ } .task-card__progress-bar-container { - padding: 1em; - width: 15rem; + padding-right: 0.5em; + padding-bottom: 0.5em; + width: 14.5rem; flex-shrink: 0; } diff --git a/package.json b/package.json index e4d944aa..b1a70313 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ "dotenv": "^16.0.1", "ember-cli-qrcode": "^2.1.0", "ember-click-outside": "^5.0.1", + "ember-resources": "^5.6.0", "mixpanel-browser": "^2.43.0", "webpack": "^5.74.0", - "webpack-5": "^0.0.0", - "ember-resources": "^5.6.0" + "webpack-5": "^0.0.0" }, "devDependencies": { "@ember/jquery": "^2.0.0", @@ -57,12 +57,14 @@ "ember-cli-dependency-checker": "^3.2.0", "ember-cli-htmlbars": "^5.3.2", "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-mirage": "^2.4.0", "ember-cli-sri": "^2.1.1", "ember-cli-terser": "^4.0.1", "ember-data": "~3.25.0", "ember-export-application-global": "^2.0.1", "ember-load-initializers": "^2.1.2", "ember-maybe-import-regenerator": "^0.1.6", + "ember-modifier": "^4.1.0", "ember-page-title": "^6.2.1", "ember-qunit": "^5.1.3", "ember-resolver": "^8.0.2", @@ -85,7 +87,6 @@ "qunit": "^2.14.0", "qunit-dom": "^1.6.0", "toastr": "^2.1.4", - "ember-cli-mirage": "^2.4.0", "tracked-maps-and-sets": "^3.0.2" }, "engines": { diff --git a/tests/integration/components/progress-bar-test.js b/tests/integration/components/progress-bar-test.js new file mode 100644 index 00000000..91562c94 --- /dev/null +++ b/tests/integration/components/progress-bar-test.js @@ -0,0 +1,86 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { find, render, fillIn, click, waitFor } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | progress-bar', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs``); + + assert.dom('[data-test-progress-bar]').exists(); + }); + + test('it has edit button', async function (assert) { + await render(hbs``); + + assert.dom('[data-test-progress-bar-button]').exists(); + }); + + test('it has clickable edit button', async function (assert) { + this.setProperties({ + percentageCompleted: '10', + onUpdate: (value) => { + this.percentageCompleted = value; + }, + }); + await render( + hbs`` + ); + const editButton = find('[data-test-progress-bar-button]'); + + await click(editButton); + assert.dom(editButton).containsText('10'); + }); + + test('it should display the correct value on update', async function (assert) { + this.setProperties({ + percentageCompleted: '10', + onUpdate: (value) => { + this.percentageCompleted = value; + }, + }); + await render( + hbs`` + ); + + const editButton = find('[data-test-progress-bar-button]'); + + await click(editButton); + + const progressBarInput = find('[data-test-progress-bar]'); + + await fillIn(progressBarInput, '50'); + + assert.dom('[data-test-progress-bar-button]').exists(); + assert.equal(progressBarInput.value, '50', "The value should be '50'."); + }); + + test('it should display the old value when an update fails', async function (assert) { + this.setProperties({ + percentageCompleted: '10', + onChange: () => { + this.percentageCompleted = '10'; + }, + }); + await render( + hbs`` + ); + + const editButton = find('[data-test-progress-bar-button]'); + + await click(editButton); + let progressBarInput = find('[data-test-progress-bar]'); + + await fillIn(progressBarInput, '50'); + + assert.dom('[data-test-progress-bar-button]').exists(); + + await waitFor(editButton); + + await click(editButton); + + assert.dom('[data-test-progress-bar-button]').hasText('10'); + }); +}); diff --git a/yarn.lock b/yarn.lock index 009984fd..b31ba9f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1253,6 +1253,15 @@ broccoli-funnel "^3.0.8" semver "^7.3.8" +"@embroider/addon-shim@^1.8.4": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.6.tgz#b676991b4fa32c3a98dc7db7dc6cd655029c3f09" + integrity sha512-siC9kP78uucEbpDcVyxjkwa76pcs5rVzDVpWO4PDc9EAXRX+pzmUuSTLAK3GztUwx7/PWhz1BenAivqdSvSgfg== + dependencies: + "@embroider/shared-internals" "^2.2.3" + broccoli-funnel "^3.0.8" + semver "^7.3.8" + "@embroider/macros@^0.41.0": version "0.41.0" resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-0.41.0.tgz#3e78b6f388d7229906abf4c75edfff8bb0208aca" @@ -1320,6 +1329,20 @@ semver "^7.3.5" typescript-memoize "^1.0.1" +"@embroider/shared-internals@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.2.3.tgz#2c7ffaa42066906bfc30c766450c199d9602cdd9" + integrity sha512-4RXJ07TqkQN4FpLBnQ92TZWW4wGAH7CRG37F1iE99rjxoD3IkoKe1IeyNr0Q85lws+2awx4/cEpaUsSwUYiJSg== + dependencies: + babel-import-util "^1.1.0" + ember-rfc176-data "^0.3.17" + fs-extra "^9.1.0" + js-string-escape "^1.0.1" + lodash "^4.17.21" + resolve-package-path "^4.0.1" + semver "^7.3.5" + typescript-memoize "^1.0.1" + "@embroider/util@^0.39.1 || ^0.40.0 || ^0.41.0 || ^1.0.0", "@embroider/util@^1.0.0", "@embroider/util@^1.9.0": version "1.10.0" resolved "https://registry.yarnpkg.com/@embroider/util/-/util-1.10.0.tgz#8320d73651e7f5d48dac1b71fb9e6d21cac7c803" @@ -5751,6 +5774,15 @@ ember-modifier@^3.1.0, ember-modifier@^3.2.0, ember-modifier@^3.2.7: ember-cli-typescript "^5.0.0" ember-compatibility-helpers "^1.2.5" +ember-modifier@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-4.1.0.tgz#cb91efbf8ca4ff4a1a859767afa42dddba5a2bbd" + integrity sha512-YFCNpEYj6jdyy3EjslRb2ehNiDvaOrXTilR9+ngq+iUqSHYto2zKV0rleiA1XJQ27ELM1q8RihT29U6Lq5EyqQ== + dependencies: + "@embroider/addon-shim" "^1.8.4" + ember-cli-normalize-entity-name "^1.0.0" + ember-cli-string-utils "^1.1.0" + ember-named-blocks-polyfill@^0.2.4: version "0.2.5" resolved "https://registry.yarnpkg.com/ember-named-blocks-polyfill/-/ember-named-blocks-polyfill-0.2.5.tgz#d5841406277026a221f479c815cfbac6cdcaeecb"