diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.html b/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.html index a1843623f..74084b438 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.html +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.html @@ -2,21 +2,13 @@
-
-
+
{{ 'Quantity' | translate }}
+ + +
+ + {{ 'Quantity' | translate }} + + +
diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.ts b/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.ts index d1bacc8f7..df48d9d75 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.ts +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/card-editor/card-editor.component.ts @@ -1,10 +1,7 @@ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { PicsaDialogService } from '@picsa/shared/features'; -import { - IBudgetCardDB, - IBudgetCardWithValues, -} from '../../../models/budget-tool.models'; +import { IBudgetCardDB, IBudgetCardWithValues } from '../../../models/budget-tool.models'; import { BudgetStore } from '../../../store/budget.store'; @Component({ @@ -27,12 +24,18 @@ export class BudgetCardEditorComponent { const target = e.target as HTMLInputElement; this.card.values[key] = Number(target.value); if (this.card.values.quantity && this.card.values.cost) { - this.card.values.total = - this.card.values.quantity * this.card.values.cost; + this.card.values.total = this.card.values.quantity * this.card.values.cost; } this.valueChanged.emit(this.card); } + // HACK - produceConsumed only populates quanityt, not values or cost + setProduceConsumed(e: Event) { + const target = e.target as HTMLInputElement; + this.card.values = { quantity: Number(target.value), cost: 0, total: 0 }; + this.valueChanged.emit(this.card); + } + async promptCustomDelete(e: Event) { e.stopPropagation(); const dialogRef = await this.dialog.open('delete'); diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.html b/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.html index 88e77ac39..3a04407bb 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.html +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.html @@ -1,3 +1,5 @@ +

Outputs

+
- {{'Quantity' | translate}} + {{'Consumed' | translate}} +
+
+
{{ 'Total Produced' | translate }}
+
{{ totalOutputs[card.id] | number: '.0' }}
+
+
+
{{ 'Total Consumed' | translate }}
+
{{ totalConsumed[card.id] | number: '.0' }}
+
+
-
- You have not selected any cards -
+ +
{{'No outputs produce for consumption' | translate}}
diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.ts b/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.ts index b38ca963f..300e01b1a 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.ts +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/card-select/produce-consumed/produce-consumed.ts @@ -1,5 +1,7 @@ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + import { IBudgetCardWithValues } from '../../../../models/budget-tool.models'; +import { BudgetStore } from '../../../../store/budget.store'; @Component({ selector: 'budget-cell-editor-produce-consumed', @@ -9,22 +11,99 @@ import { IBudgetCardWithValues } from '../../../../models/budget-tool.models'; /* The budget cell editor sits on top of the budget table, so that when opened covers the table */ -export class BudgetCellEditorProduceConsumedComponent { +export class BudgetCellEditorProduceConsumedComponent implements OnInit { + // Legacy version @Input() cards: IBudgetCardWithValues[] = []; + @Output() valueChanged = new EventEmitter(); + public totalOutputs: Record; + public totalConsumed: Record; + + constructor(private store: BudgetStore) {} + ngOnInit(): void { - //Called after the constructor, initializing input properties, and the first call to ngOnChanges. - //Add 'implements OnInit' to the class. - console.log('cards', this.cards); + const { cards } = this.generateProduceConsumedCards(); + this.cards = cards; + } + + /** + * Generate a list of cards to be used for produce consumed selection + * The list restricts to only cards that have had outputs produced within + * the active period, and tracks total quanitities available/consumed + */ + private generateProduceConsumedCards() { + const allBudgetPeriods = this.store.activeBudget.data || []; + + // Extract values for any existing produce consumed values + const periodConsumed: Record = {}; + const currentPeriodData = allBudgetPeriods[this.store.activePeriod]; + for (const card of currentPeriodData.produceConsumed) { + // HACK - Legacy formatting stored consumed quantity at top-level and not within values + const legacyQuantity = card['quantity' as string]; + if (legacyQuantity) card.values = { quantity: legacyQuantity, cost: 0, total: 0 }; + periodConsumed[card.id] = card.values.quantity || 0; + } + + this.totalOutputs = this.calcTotalOutputs(); + this.totalConsumed = this.calcTotalConsumed(); + + // Create a list of produceConsumed cards from list of output card, including only those + // that have had outputs produced and assigned with existing period values + const cards = this.store.budgetCardsByType.outputs + .filter(({ id }) => id in this.totalOutputs && id !== 'money') + .map((c) => { + const card = c as IBudgetCardWithValues; + card.type = 'produceConsumed'; + card.values = { quantity: periodConsumed[c.id] || 0, cost: 0, total: 0 }; + return card; + }); + + return { cards }; + } + + private calcTotalOutputs() { + const allBudgetPeriods = this.store.activeBudget.data || []; + // sum all quantities of all outputs up to current period and store in a list + // TODO - could also track totalConsumed and use for data validation + const totalOutputs: Record = {}; + const consumablePeriods = allBudgetPeriods.filter((_, i) => i <= this.store.activePeriod); + for (const { outputs } of consumablePeriods) { + for (const card of outputs) { + totalOutputs[card.id] ??= 0; + totalOutputs[card.id] += card.values.quantity; + } + } + return totalOutputs; + } + + private calcTotalConsumed() { + const allBudgetPeriods = this.store.activeBudget.data || []; + // sum all quantities of all outputs up to current period and store in a list + // TODO - could also track totalConsumed and use for data validation + const totalOutputs: Record = {}; + const consumablePeriods = allBudgetPeriods.filter((_, i) => i <= this.store.activePeriod); + for (const { produceConsumed } of consumablePeriods) { + for (const card of produceConsumed) { + // HACK - Legacy formatting stored consumed quantity at top-level and not within values + const legacyQuantity = card['quantity' as string]; + if (legacyQuantity) card.values = { quantity: legacyQuantity, cost: 0, total: 0 }; + totalOutputs[card.id] ??= 0; + totalOutputs[card.id] += card.values.quantity; + } + } + return totalOutputs; } // using manual bindings instead of ngmodel as nested ngfor-ngmodel with matInput tricky - setValue(e: Event, key: 'quantity' | 'cost', cardIndex: number) { + setValue(e: Event, cardIndex: number) { const card = this.cards[cardIndex]; const target = e.target as HTMLInputElement; - card.values[key] = Number(target.value); + card.values.quantity = Number(target.value); this.cards[cardIndex] = card; - this.valueChanged.emit(this.cards); + // only emit cards with quantities specified + const consumedCards = this.cards.filter((c) => c.values.quantity); + this.valueChanged.emit(consumedCards); + this.generateProduceConsumedCards(); } } diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.html b/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.html index f2177316d..adacbf031 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.html +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.html @@ -2,17 +2,16 @@
{{ editorStep.label | translate }}
-
+
-
@@ -38,7 +37,6 @@ diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.scss b/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.scss index 975adb154..aadc5c457 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.scss +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.scss @@ -68,4 +68,8 @@ grid-template-columns: repeat(auto-fit, minmax(275px, 1fr)); gap: 16px; } + &[data-type='produceConsumed'] { + grid-template-columns: repeat(auto-fit, minmax(275px, 1fr)); + gap: 16px; + } } diff --git a/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.ts b/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.ts index 908350dd3..f0a4b10fb 100644 --- a/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.ts +++ b/apps/picsa-tools/budget-tool/src/app/components/editor/editor.component.ts @@ -1,5 +1,8 @@ import { Component, ElementRef, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core'; -import { FadeInOut, ANIMATION_DELAYED } from '@picsa/shared/animations'; +import { MatDialog } from '@angular/material/dialog'; +import { ANIMATION_DELAYED, FadeInOut } from '@picsa/shared/animations'; +import { _wait } from '@picsa/utils'; + import { IBudgetCard, IBudgetCardWithValues, @@ -7,8 +10,6 @@ import { IBudgetPeriodType, } from '../../models/budget-tool.models'; import { BudgetStore } from '../../store/budget.store'; -import { MatDialog } from '@angular/material/dialog'; -import { _wait } from '@picsa/utils'; const EDITOR_STEPS: { type: IBudgetPeriodType; label: string }[] = [ { type: 'activities', label: 'Activities' }, @@ -110,9 +111,12 @@ export class BudgetEditorComponent { this.loadEditorData(); } - // the store already knows what period and type it is, so just pass the updated values - // back up to save + // the store already knows what period and type it is, so just pass the updated values to save onEditorChange(values: IBudgetCardWithValues[], type: IBudgetPeriodType) { this.store.saveEditor(values, type); + // HACK - fix change detection issue for produceConsumed cards + if (type === 'produceConsumed') { + this.data.produceConsumed = values; + } } } diff --git a/apps/picsa-tools/budget-tool/src/app/models/budget-tool.models.ts b/apps/picsa-tools/budget-tool/src/app/models/budget-tool.models.ts index 68f4c854d..ae8ba1a37 100644 --- a/apps/picsa-tools/budget-tool/src/app/models/budget-tool.models.ts +++ b/apps/picsa-tools/budget-tool/src/app/models/budget-tool.models.ts @@ -69,7 +69,6 @@ export type IBudgetCardType = IBudgetPeriodType | 'enterprise' | 'other'; export interface IBudgetCardWithValues extends IBudgetCard { values: IBudgetCardValues; - quantity?: number; // possible legacy use? (e.g. produce-consumed) } interface IBudgetCardCustomMeta { diff --git a/apps/picsa-tools/budget-tool/src/app/store/budget.store.ts b/apps/picsa-tools/budget-tool/src/app/store/budget.store.ts index cd8db737f..ea33140c3 100644 --- a/apps/picsa-tools/budget-tool/src/app/store/budget.store.ts +++ b/apps/picsa-tools/budget-tool/src/app/store/budget.store.ts @@ -70,7 +70,6 @@ export class BudgetStore implements OnDestroy { @observable balance: IBudgetBalance = []; // get unique list of types in enterprise cards @computed get enterpriseTypeCards(): IBudgetCardDB[] { - console.log('get enterprisetype cards'); const enterpriseCards = this.budgetCards.filter((c) => c.type === 'enterprise'); return this._createCardGroupCards(enterpriseCards); } diff --git a/libs/i18n/assets/en.json b/libs/i18n/assets/en.json index 5cc4293ea..077e0f8f0 100644 --- a/libs/i18n/assets/en.json +++ b/libs/i18n/assets/en.json @@ -98,6 +98,7 @@ "Length": "Length", "Male Member": "Male Member", "Next": "Next", + "No outputs produce for consumption": "No outputs produce for consumption", "Outputs": "Outputs", "Produce Consumed": "Produce Consumed", "Quantity": "Quantity", @@ -112,6 +113,8 @@ "Summary": "Summary", "Title": "Title", "Total": "Total", + "Total Consumed": "Total Consumed", + "Total Produced": "Total Produced", "Values": "Values", "What is your type of enterprise?": "What is your type of enterprise?", "description": "description", diff --git a/libs/i18n/assets/ny.json b/libs/i18n/assets/ny.json index 98c3aadfe..0152cf86a 100644 --- a/libs/i18n/assets/ny.json +++ b/libs/i18n/assets/ny.json @@ -4,8 +4,11 @@ "Family Labour": "", "Female Member": "", "Male Member": "", + "No outputs produce for consumption": "", "Produce Consumed": "", "Select Country": "", + "Total Consumed": "", + "Total Produced": "", "Activities": "zochitika", "add custom": "onjezani mwambo", "afforestation": "kugogoda", diff --git a/libs/i18n/assets/sw.json b/libs/i18n/assets/sw.json index 98978f677..c6e37047b 100644 --- a/libs/i18n/assets/sw.json +++ b/libs/i18n/assets/sw.json @@ -19,6 +19,7 @@ "Line": "", "Male Member": "", "Monitoring": "", + "No outputs produce for consumption": "", "On average": "", "onions": "", "out of": "", @@ -43,6 +44,8 @@ "Terciles": "", "title": "", "Total": "", + "Total Consumed": "", + "Total Produced": "", "Values": "", "Year": "", "years out of every 10": "", diff --git a/libs/i18n/assets/tg.json b/libs/i18n/assets/tg.json index 200becb20..a9255d0ad 100644 --- a/libs/i18n/assets/tg.json +++ b/libs/i18n/assets/tg.json @@ -19,6 +19,7 @@ "Line": "", "Male Member": "", "Monitoring": "", + "No outputs produce for consumption": "", "On average": "", "onions": "", "out of": "", @@ -43,6 +44,8 @@ "Terciles": "", "title": "", "Total": "", + "Total Consumed": "", + "Total Produced": "", "Values": "", "Year": "", "years out of every 10": "", diff --git a/libs/i18n/templates/_template.csv b/libs/i18n/templates/_template.csv index 135220467..75bd22200 100644 --- a/libs/i18n/templates/_template.csv +++ b/libs/i18n/templates/_template.csv @@ -98,6 +98,7 @@ tool,context,text "budget",,"Length" "budget",,"Male Member" "budget",,"Next" +"budget",,"No outputs produce for consumption" "budget",,"Outputs" "budget",,"Produce Consumed" "budget",,"Quantity" @@ -112,6 +113,8 @@ tool,context,text "budget",,"Summary" "budget",,"Title" "budget",,"Total" +"budget",,"Total Consumed" +"budget",,"Total Produced" "budget",,"Values" "budget",,"What is your type of enterprise?" "budget",,"description" diff --git a/libs/i18n/templates/_template.json b/libs/i18n/templates/_template.json index a232cc744..42ccbce42 100644 --- a/libs/i18n/templates/_template.json +++ b/libs/i18n/templates/_template.json @@ -473,6 +473,10 @@ "text": "Next", "tool": "budget" }, + { + "text": "No outputs produce for consumption", + "tool": "budget" + }, { "text": "Outputs", "tool": "budget" @@ -529,6 +533,14 @@ "text": "Total", "tool": "budget" }, + { + "text": "Total Consumed", + "tool": "budget" + }, + { + "text": "Total Produced", + "tool": "budget" + }, { "text": "Values", "tool": "budget"