diff --git a/BUILDING.md b/BUILDING.md index eced705..6949b3f 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -338,11 +338,7 @@ Run local tests: npm run test-local ``` -Run all tests (includes building): - -```bash -npm run test-all -``` +See [TESTING.md](test-playwright/TESTING.md) for more details. ## Linting diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6c083..c4c03a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [0.4.5] - 2025.02.23 +### Added +- Total amounts of gives on project page +### Changed in DB or environment +- Requires Endorser.ch version 4.2.6+ + + ## [0.4.4] - 2025.02.17 ### Fixed in 0.4.4 diff --git a/README.md b/README.md index 21c0cd4..1f299ad 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,15 @@ npm install npm run dev ``` -See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use for local endorser.ch +See [BUILDING.md](BUILDING.md) for more details. -### Build the test & production app +See the test locations for "IMAGE_API_SERVER" or "PARTNER_API_SERVER" below, or use http://localhost:3000 for local endorser.ch -```bash -npm run serve -``` - -### Lint and fix files - -```bash -npm run lint -``` ### Run all UI tests -Look below for the "test-all" instructions. +Look at [BUILDING.md](BUILDING.md) for the "test-all" instructions and [TESTING.md](test-playwright/TESTING.md) for more details. + ### Compile and minify for test & production @@ -79,10 +71,17 @@ TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari. * Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production. + + + + ## Tests See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions. + + + ## Icons To add an icon, add to main.ts and reference with `fa` element and `icon` attribute with the hyphenated name. diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index c0fcd6b..1b0c28a 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -355,83 +355,139 @@ -

Given To This Idea

+

Given To This Idea

-
+
(None yet. If you've seen something, say something by clicking a contact above.)
- + + +
@@ -646,7 +702,11 @@ export default class ProjectViewView extends Vue { fulfillersToThis: Array = []; /** Flag for fulfiller pagination */ fulfillersToHitLimit = false; - /** Project image URL */ + givesToThis: Array = []; + givesHitLimit = false; + givesProvidedByThis: Array = []; + givesProvidedByHitLimit = false; + givesTotalsByUnit: Array<{ unit: string; amount: number }> = []; imageUrl = ""; /** Project issuer DID */ issuer = ""; @@ -660,6 +720,7 @@ export default class ProjectViewView extends Vue { issuerVisibleToDids: Array = []; /** Project location data */ latitude = 0; + loadingTotals = false; longitude = 0; /** Project name */ name = ""; @@ -689,6 +750,8 @@ export default class ProjectViewView extends Vue { checkingConfirmationForJwtId = ""; /** Recently checked unconfirmable JWTs */ recentlyCheckedAndUnconfirmableJwts: string[] = []; + startTime = ""; + totalsExpanded = false; truncatedDesc = ""; /** Truncation length */ truncateLength = 40; @@ -740,21 +803,26 @@ export default class ProjectViewView extends Vue { this.projectId = decodeURIComponent(pathParam); } this.loadProject(this.projectId, this.activeDid); + this.loadTotals(); + } + + onEditClick() { + const route = { + name: "new-edit-project", + query: { projectId: this.projectId }, + }; + (this.$router as Router).push(route); + } + + // Isn't there a better way to make this available to the template? + expandText() { + this.expanded = true; + } + + collapseText() { + this.expanded = false; } - /** - * Loads project data and related information - * - * Workflow: - * 1. Fetches project details from API - * 2. Updates component state with project data - * 3. Initializes related data loading (gifts, offers, fulfillments) - * - * @param projectId Project handle ID - * @param userDid Active user's DID - * @throws Logs errors and notifies user - * @emits Notification on loading errors - */ async loadProject(projectId: string, userDid: string) { this.projectId = projectId; @@ -1394,5 +1462,56 @@ export default class ProjectViewView extends Vue { this.allMyDids, ); } + + async loadTotals() { + this.loadingTotals = true; + const url = + this.apiServer + + "/api/v2/report/givesToPlans?planIds=" + + encodeURIComponent(JSON.stringify([this.projectId])); + const headers = await serverUtil.getHeaders(this.activeDid); + + try { + const resp = await this.axios.get(url, { headers }); + if (resp.status === 200 && resp.data.data) { + // Calculate totals by unit + const totals: { [key: string]: number } = {}; + resp.data.data.forEach((give: GiveSummaryRecord) => { + const amount = give.fullClaim.object?.amountOfThisGood; + const unit = give.fullClaim.object?.unitCode; + if (amount && unit) { + totals[unit] = (totals[unit] || 0) + amount; + } + }); + + // Convert totals object to array format + this.givesTotalsByUnit = Object.entries(totals).map( + ([unit, amount]) => ({ + unit, + amount, + }), + ); + } + } catch (error) { + console.error("Error loading totals:", error); + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "Failed to load totals for this project.", + }, + 5000, + ); + } finally { + this.loadingTotals = false; + } + } + + givenTotalHours(): number { + return ( + this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0 + ); + } } diff --git a/test-playwright/35-record-gift-from-image-share.spec.ts b/test-playwright/35-record-gift-from-image-share.spec.ts index bdece66..f1de7fb 100644 --- a/test-playwright/35-record-gift-from-image-share.spec.ts +++ b/test-playwright/35-record-gift-from-image-share.spec.ts @@ -48,7 +48,7 @@ */ import path from 'path'; import { test, expect } from '@playwright/test'; -import { importUser } from './testUtils'; +import { importUserAndCloseOnboarding } from './testUtils'; /** * Note: by default, this test uses the test image API server. @@ -65,7 +65,7 @@ test('Record item given from image-share', async ({ page }) => { // Combine title prefix with the random string const finalTitle = `Gift ${randomString} from image-share`; - await importUser(page, '00'); + await importUserAndCloseOnboarding(page, '00'); // Record something given await page.goto('./test'); @@ -84,10 +84,8 @@ test('Record item given from image-share', async ({ page }) => { await page.getByRole('spinbutton').fill('2'); await page.getByRole('button', { name: 'Sign & Send' }).click(); - // we end up on a page with the onboarding info - await page.getByTestId('closeOnboardingAndFinish').click(); - - await expect(page.getByText('That gift was recorded.')).toBeVisible(); + // const recorded = await page.getByText('That gift was recorded.'); + await expect(await page.getByText('That gift was recorded.')).toBeVisible(); await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert // Refresh home view and check gift diff --git a/test-playwright/40-add-contact.spec.ts b/test-playwright/40-add-contact.spec.ts index 8d5416a..5f2fe1e 100644 --- a/test-playwright/40-add-contact.spec.ts +++ b/test-playwright/40-add-contact.spec.ts @@ -207,7 +207,6 @@ test('Add contact, copy details, delete, and import from paste & from file', asy // Add another new contact await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b, User #222, asdf1234'); await page.locator('button > svg.fa-plus').click(); - await expect(page.locator('div[role="alert"]')).toBeVisible(); await expect(page.locator('div[role="alert"] span:has-text("No")')).toBeVisible(); await page.locator('div[role="alert"] button:has-text("No")').click(); // don't register await expect(page.locator('div[role="alert"] span:has-text("Contact Added")')).toBeVisible(); diff --git a/test-playwright/testUtils.ts b/test-playwright/testUtils.ts index 654f0c8..13cced1 100644 --- a/test-playwright/testUtils.ts +++ b/test-playwright/testUtils.ts @@ -33,6 +33,13 @@ export async function importUser(page: Page, id?: string): Promise { return did; } +export async function importUserAndCloseOnboarding(page: Page, id?: string): Promise { + const did = await importUser(page, id); + await page.goto('./'); + await page.getByTestId('closeOnboardingAndFinish').click(); + return did; +} + // This is to switch to someone already in the identity table. It doesn't include registration. export async function switchToUser(page: Page, did: string): Promise { // This is the direct approach but users have to tap on things so we'll do that instead.