+
= {{ values[i] | number: '.0' }}
diff --git a/apps/picsa-tools/budget-tool/src/app/components/budget-tool.components.ts b/apps/picsa-tools/budget-tool/src/app/components/budget-tool.components.ts
index 79d2cc84a..18ff30512 100644
--- a/apps/picsa-tools/budget-tool/src/app/components/budget-tool.components.ts
+++ b/apps/picsa-tools/budget-tool/src/app/components/budget-tool.components.ts
@@ -1,62 +1,71 @@
import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { IonicModule } from '@ionic/angular';
import { CanvasWhiteboardModule } from 'ng2-canvas-whiteboard';
-import { BudgetCardImageComponent } from './card/card-image/budget-card-image';
-import { BudgetCardComponent } from './card/budget-card';
-import { BudgetCellComponent } from './cell/cell';
-import { BudgetTableComponent } from './table/budget-table';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
+import { MobxAngularModule } from 'mobx-angular';
+import { PicsaDbModule } from '@picsa/shared/modules';
+import { PicsaDialogsModule } from '@picsa/shared/features';
+import { PicsaCommonComponentsModule } from '@picsa/components';
+
import { BudgetMaterialModule } from '../material.module';
-import {
- BudgetListItemComponent,
- BudgetListItemRenameDialog,
-} from './list-item/budget-list-item';
-import { BudgetCellLabourComponent } from './cell/variants/labour/labour';
+
+// Components
import { BudgetCellEditorComponent } from './_deprecated/cell-editor';
import { NextButton } from './_deprecated/next-button';
-import { MobxAngularModule } from 'mobx-angular';
-import { PicsaDbModule } from '@picsa/shared/modules';
+import { BudgetBalanceDotValueComponent } from './balance/balance-dot-value/dot-value';
import {
BudgetBalanceLegendComponent,
BudgetBalanceEditorComponent,
} from './balance/balance-legend/balance-legend';
+import { BudgetCardComponent } from './card/budget-card';
+import { BudgetCardImageComponent } from './card/card-image/budget-card-image';
+import { BudgetCardNew, BudgetCardNewDialog } from './card/card-new/card-new';
+import { BudgetCellComponent } from './cell/cell';
+import { BudgetCellLabourComponent } from './cell/variants/labour/labour';
+import { BudgetCellEditorCardSelectComponent } from './cell-editor/card-select/card-select';
import { BudgetCellEditorInputValuesComponent } from './cell-editor/input-values/input-values';
-import { BudgetBalanceDotValueComponent } from './balance/balance-dot-value/dot-value';
import { BudgetCellEditorFamilyLabourComponent } from './cell-editor/family-labour/family-labour';
import { BudgetCellEditorProduceConsumedComponent } from './cell-editor/produce-consumed/produce-consumed';
-import { BudgetCardNew, BudgetCardNewDialog } from './card/card-new/card-new';
-import { RouterModule } from '@angular/router';
-import { BudgetCellEditorCardSelectComponent } from './cell-editor/card-select/card-select';
-import { BudgetPeriodSummaryComponent } from './summary/period-summary';
-import { PicsaDialogsModule } from '@picsa/shared/features';
-import { PicsaCommonComponentsModule } from '@picsa/components';
import { BudgetCellInlineEditorComponent } from './cell-inline-editor/cell-inline-editor.component';
+import { BudgetImportDialogComponent } from './import-dialog/import-dialog.component';
+import {
+ BudgetListItemComponent,
+ BudgetListItemRenameDialog,
+} from './list-item/budget-list-item';
+import { BudgetShareDialogComponent } from './share-dialog/share-dialog.component';
+import { BudgetPeriodSummaryComponent } from './summary/period-summary';
+import { BudgetTableComponent } from './table/budget-table';
+
+const components = [
+ BudgetBalanceLegendComponent,
+ BudgetBalanceDotValueComponent,
+ BudgetBalanceEditorComponent,
+ BudgetCardComponent,
+ BudgetCardImageComponent,
+ BudgetCellComponent,
+ BudgetCellEditorComponent,
+ BudgetCellEditorCardSelectComponent,
+ BudgetCellEditorInputValuesComponent,
+ BudgetCellEditorFamilyLabourComponent,
+ BudgetCellEditorProduceConsumedComponent,
+ BudgetCellInlineEditorComponent,
+ BudgetCellLabourComponent,
+ BudgetCardNew,
+ BudgetCardNewDialog,
+ BudgetImportDialogComponent,
+ BudgetListItemComponent,
+ BudgetListItemRenameDialog,
+ BudgetPeriodSummaryComponent,
+ BudgetShareDialogComponent,
+ BudgetTableComponent,
+ NextButton,
+];
@NgModule({
- declarations: [
- BudgetBalanceLegendComponent,
- BudgetBalanceDotValueComponent,
- BudgetBalanceEditorComponent,
- BudgetCardComponent,
- BudgetCardImageComponent,
- BudgetCellComponent,
- BudgetCellEditorComponent,
- BudgetCellEditorCardSelectComponent,
- BudgetCellEditorInputValuesComponent,
- BudgetCellEditorFamilyLabourComponent,
- BudgetCellEditorProduceConsumedComponent,
- BudgetCellInlineEditorComponent,
- BudgetCellLabourComponent,
- BudgetCardNew,
- BudgetCardNewDialog,
- BudgetListItemComponent,
- BudgetListItemRenameDialog,
- BudgetPeriodSummaryComponent,
- BudgetTableComponent,
- NextButton,
- ],
+ declarations: components,
imports: [
IonicModule,
CommonModule,
@@ -70,25 +79,6 @@ import { BudgetCellInlineEditorComponent } from './cell-inline-editor/cell-inlin
PicsaDbModule,
RouterModule,
],
- exports: [
- BudgetBalanceLegendComponent,
- BudgetBalanceDotValueComponent,
- BudgetCardComponent,
- BudgetCardImageComponent,
- BudgetCellComponent,
- BudgetCellEditorComponent,
- BudgetCellEditorCardSelectComponent,
- BudgetCellEditorInputValuesComponent,
- BudgetCellEditorFamilyLabourComponent,
- BudgetCellEditorProduceConsumedComponent,
- BudgetCellInlineEditorComponent,
- BudgetCellLabourComponent,
- BudgetListItemComponent,
- BudgetCardNew,
- BudgetTableComponent,
- BudgetPeriodSummaryComponent,
- NextButton,
- PicsaCommonComponentsModule,
- ],
+ exports: components,
})
export class BudgetToolComponentsModule {}
diff --git a/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.html b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.html
new file mode 100644
index 000000000..e0c4d0d32
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.html
@@ -0,0 +1,33 @@
+
+
{{ status }}
+
+
+
+
+
diff --git a/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.scss b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.scss
new file mode 100644
index 000000000..0ddf8689f
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.scss
@@ -0,0 +1,25 @@
+.input-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 8px;
+}
+mat-form-field.input-field {
+ width: 55px;
+}
+mat-form-field.input-field .mat-form-field-wrapper {
+ padding-bottom: 0;
+ background-color: yellow;
+}
+input {
+ font-size: 32px;
+}
+
+.status-message {
+ height: 1em;
+ line-height: 1em;
+ text-align: center;
+ color: var(--color-primary);
+ padding: 8px;
+ border-radius: 4px;
+ margin-bottom: -12px;
+}
diff --git a/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.spec.ts b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.spec.ts
new file mode 100644
index 000000000..ac90875e4
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ImportDialogComponent } from './import-dialog.component';
+
+describe('ImportDialogComponent', () => {
+ let component: ImportDialogComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ImportDialogComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ImportDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.ts b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.ts
new file mode 100644
index 000000000..6ce58bbc0
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/import-dialog/import-dialog.component.ts
@@ -0,0 +1,85 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { _wait } from '@picsa/utils';
+import { BudgetStore } from '../../store/budget.store';
+
+@Component({
+ selector: 'budget-import-dialog',
+ templateUrl: './import-dialog.component.html',
+ styleUrls: ['./import-dialog.component.scss'],
+})
+export class BudgetImportDialogComponent {
+ public status = '';
+ public disabled = false;
+
+ // store import code as separate elements to make it easier to fill one-by-one
+ public importCode = [
+ { name: 'code-input-0', value: '' },
+ { name: 'code-input-1', value: '' },
+ { name: 'code-input-2', value: '' },
+ { name: 'code-input-3', value: '' },
+ ];
+
+ constructor(private store: BudgetStore, private router: Router) {}
+
+ public async handleImport() {
+ const code = this.importValue;
+ this.status = 'Importing...';
+ this.disabled = true;
+ await _wait(200);
+ const budget = await this.store.loadBudgetByShareCode(code);
+ if (budget) {
+ this.router.navigate([location.pathname, 'view', budget._key]);
+ this.status = 'Import success ';
+ } else {
+ this.status = 'Code not found';
+ this.disabled = false;
+ }
+ }
+
+ /** On keyup go to next input box */
+ public handleKeyup(event: KeyboardEvent, index: number) {
+ this.status = '';
+ if (event.code !== 'Backspace') {
+ const focusElementId = this.importCode[index + 1]?.name;
+ if (focusElementId) {
+ const focusEl = document.getElementById(focusElementId);
+ if (focusEl) {
+ focusEl.focus();
+ }
+ }
+ }
+ }
+ /** On keydown go to previous element if backspace key pressed and value already empty */
+ public handleKeydown(event: KeyboardEvent, index: number) {
+ if (event.code === 'Backspace') {
+ const value = this.importCode[index]?.value;
+ if (!value) {
+ const focusElementId = this.importCode[index - 1]?.name;
+ if (focusElementId) {
+ const focusEl = document.getElementById(focusElementId);
+ if (focusEl) {
+ focusEl.focus();
+ }
+ }
+ }
+ }
+ }
+ public handlePaste(event: ClipboardEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ const text = event.clipboardData?.getData('text').trim();
+ if (text) {
+ this.importCode = this.importCode.map((v, i) => ({
+ name: v.name,
+ value: text.charAt(i),
+ }));
+ }
+ }
+ public get importValue() {
+ return this.importCode
+ .map((v) => v.value)
+ .join('')
+ .toUpperCase();
+ }
+}
diff --git a/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.html b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.html
new file mode 100644
index 000000000..e2b2d6a1d
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.html
@@ -0,0 +1,43 @@
+{{ 'Share Budget' | translate }}
+
+
+
Share Code
+ {{ shareCode }}
+
+{{ status }}
+
+
+
diff --git a/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.scss b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.scss
new file mode 100644
index 000000000..ce5306935
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.scss
@@ -0,0 +1,24 @@
+img[data-disabled] {
+ filter: grayscale(1);
+}
+
+.status-message {
+ height: 1em;
+ line-height: 1em;
+ text-align: center;
+ color: var(--color-primary);
+ padding: 8px;
+ border-radius: 4px;
+ margin-bottom: -12px;
+}
+
+.button-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ width: 280px;
+}
+
+.share-code-box {
+ border: 2px dashed;
+ text-align: center;
+}
diff --git a/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.spec.ts b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.spec.ts
new file mode 100644
index 000000000..1cd9d7880
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ShareDialogComponent } from './share-dialog.component';
+
+describe('ShareDialogComponent', () => {
+ let component: ShareDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ShareDialogComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ShareDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.ts b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.ts
new file mode 100644
index 000000000..cf829df5d
--- /dev/null
+++ b/apps/picsa-tools/budget-tool/src/app/components/share-dialog/share-dialog.component.ts
@@ -0,0 +1,46 @@
+import { Component } from '@angular/core';
+import { _wait } from '@picsa/utils';
+import { BudgetStore } from '../../store/budget.store';
+
+@Component({
+ selector: 'budget-share-dialog',
+ templateUrl: './share-dialog.component.html',
+ styleUrls: ['./share-dialog.component.scss'],
+})
+export class BudgetShareDialogComponent {
+ public status = '';
+ public disabled = false;
+ public shareCode: string;
+
+ constructor(private store: BudgetStore) {}
+
+ public async sharePicture() {
+ this.disabled = true;
+ this.status = 'Preparing image....';
+ await _wait(200);
+
+ try {
+ await this.store.shareAsImage();
+ this.disabled = false;
+ this.status = '';
+ } catch (error: any) {
+ this.status = error?.message || 'Unable to share';
+ this.disabled = false;
+ }
+ }
+
+ public async shareLink() {
+ this.disabled = true;
+ this.status = 'Preparing link....';
+ await _wait(200);
+ try {
+ const shareCode = await this.store.shareAsLink();
+ this.shareCode = shareCode;
+ this.disabled = false;
+ this.status = '';
+ } catch (error: any) {
+ this.status = error?.message || 'Unable to share';
+ this.disabled = false;
+ }
+ }
+}
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 62bcb863a..68f4c854d 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
@@ -4,6 +4,7 @@ export interface IBudget extends IDBDoc {
data: IBudgetPeriodData[];
meta: IBudgetMeta;
apiVersion: number;
+ shareCode?: string;
_appVersion: string;
}
@@ -93,3 +94,7 @@ interface IBudgetPeriodBalance {
period: number;
running: number;
}
+
+export interface IBudgetCodeDoc extends IDBDoc {
+ budget_key: string;
+}
diff --git a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.module.ts b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.module.ts
index d9320950b..10345438a 100644
--- a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.module.ts
+++ b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.module.ts
@@ -7,6 +7,7 @@ import { BudgetMaterialModule } from '../../material.module';
import { MobxAngularModule } from 'mobx-angular';
import { PicsaTranslateModule } from '@picsa/shared/modules/translate';
import { PicsaDialogsModule } from '@picsa/shared/features';
+import { FormsModule } from '@angular/forms';
const routes: Routes = [
{
@@ -19,6 +20,7 @@ const routes: Routes = [
imports: [
CommonModule,
RouterModule.forChild(routes),
+ FormsModule,
PicsaTranslateModule,
PicsaDialogsModule,
BudgetMaterialModule,
diff --git a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.html b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.html
index a16258ed0..6f70d1804 100644
--- a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.html
+++ b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.html
@@ -1,8 +1,14 @@
-
+
{{ 'Saved Budgets' | translate }}
diff --git a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.scss b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.scss
index 658f093b7..9fb0de596 100644
--- a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.scss
+++ b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.scss
@@ -8,4 +8,13 @@
button.nav-icon[disabled] {
opacity: 0;
-}
\ No newline at end of file
+}
+.button-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+}
+.button-grid img {
+ height: 100px;
+ margin-bottom: 8px;
+}
diff --git a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.ts b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.ts
index 703de3d61..bc040967d 100644
--- a/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.ts
+++ b/apps/picsa-tools/budget-tool/src/app/pages/home/budget-home.page.ts
@@ -2,8 +2,9 @@ import { Component } from '@angular/core';
import { IBudget } from '../../models/budget-tool.models';
import { BudgetStore } from '../../store/budget.store';
import { Router, ActivatedRoute } from '@angular/router';
-import { PrintProvider } from '@picsa/shared/services/native/print';
import { PicsaDialogService } from '@picsa/shared/features';
+import { BudgetImportDialogComponent } from '../../components/import-dialog/import-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'budget-home',
templateUrl: './budget-home.page.html',
@@ -13,11 +14,11 @@ export class BudgetHomePage {
sharedDisabled: boolean;
budgetDownloadMessage: string;
constructor(
- private printPrvdr: PrintProvider,
public store: BudgetStore,
private router: Router,
private route: ActivatedRoute,
- private dialog: PicsaDialogService
+ private dialog: PicsaDialogService,
+ private matDialog: MatDialog
) {}
createClicked() {
@@ -31,6 +32,9 @@ export class BudgetHomePage {
}
});
}
+ public importBudgetCode() {
+ this.matDialog.open(BudgetImportDialogComponent);
+ }
async deleteBudget(budget: IBudget) {
this.store.deleteBudget(budget);
diff --git a/apps/picsa-tools/budget-tool/src/app/pages/view/budget-view.page.html b/apps/picsa-tools/budget-tool/src/app/pages/view/budget-view.page.html
index 8877425f1..c56e69c5a 100644
--- a/apps/picsa-tools/budget-tool/src/app/pages/view/budget-view.page.html
+++ b/apps/picsa-tools/budget-tool/src/app/pages/view/budget-view.page.html
@@ -5,7 +5,6 @@
*ngIf="!isEditorOpen"
aria-label="Share"
(click)="showShareDialog()"
- [disabled]="isSharing"
>
shareShare
@@ -22,7 +21,7 @@
@fadeInOut
>