add project selection for one that this 'fulfills'

This commit is contained in:
2026-03-22 17:58:46 -06:00
parent e259e60fa7
commit 05d346edce
5 changed files with 97 additions and 8 deletions

View File

@@ -39,7 +39,7 @@ import { PlanData } from "../interfaces/records";
import { NotificationIface } from "../constants/app"; import { NotificationIface } from "../constants/app";
/** /**
* MeetingProjectDialog - Dialog for selecting a project link for a meeting * ProjectSelectionDialog - Dialog for selecting a project
* *
* Features: * Features:
* - EntityGrid integration for project selection * - EntityGrid integration for project selection
@@ -52,7 +52,7 @@ import { NotificationIface } from "../constants/app";
EntityGrid, EntityGrid,
}, },
}) })
export default class MeetingProjectDialog extends Vue { export default class ProjectSelectionDialog extends Vue {
/** Whether the dialog is visible */ /** Whether the dialog is visible */
visible = false; visible = false;

View File

@@ -80,6 +80,7 @@ export interface PlanActionClaim extends ClaimObject {
agent?: { identifier: string }; agent?: { identifier: string };
description?: string; description?: string;
endTime?: string; endTime?: string;
fulfills?: { "@type": string; identifier?: string; lastClaimId?: string };
identifier?: string; identifier?: string;
image?: string; image?: string;
lastClaimId?: string; lastClaimId?: string;

View File

@@ -114,6 +114,49 @@
@assign="handleRepresentativeAssigned" @assign="handleRepresentativeAssigned"
/> />
<!-- Parent Project Selection -->
<div class="w-full flex items-stretch my-4">
<div
class="flex items-center gap-2 grow border border-slate-400 border-r-0 last:border-r px-3 py-2 rounded-l last:rounded overflow-hidden cursor-pointer hover:bg-slate-100"
@click="openParentProjectDialog"
>
<div>
<font-awesome icon="folder" class="text-slate-400" />
</div>
<div class="overflow-hidden">
<div
:class="{
'text-sm font-semibold': parentProjectHandleId,
'text-slate-400': !parentProjectHandleId,
}"
class="truncate"
>
{{
parentProjectHandleId
? parentProjectName || "Parent Project"
: "Select Parent Project\u2026"
}}
</div>
</div>
</div>
<button
v-if="parentProjectHandleId"
class="text-rose-600 px-3 py-2 border border-slate-400 border-l-0 rounded-r hover:bg-rose-600 hover:text-white hover:border-rose-600"
@click="unsetParentProject"
>
<font-awesome icon="trash-can" />
</button>
</div>
<ProjectSelectionDialog
ref="parentProjectDialog"
:active-did="activeDid"
:all-my-dids="allMyDids"
:all-contacts="allContacts"
:notify="$notify"
@assign="handleParentProjectSelected"
/>
<div class="mb-4"> <div class="mb-4">
<p v-if="shouldShowOwnershipWarning"> <p v-if="shouldShowOwnershipWarning">
<span class="text-red-500">Beware!</span> <span class="text-red-500">Beware!</span>
@@ -283,6 +326,7 @@ import { LeafletMouseEvent } from "leaflet";
import EntityIcon from "../components/EntityIcon.vue"; import EntityIcon from "../components/EntityIcon.vue";
import ImageMethodDialog from "../components/ImageMethodDialog.vue"; import ImageMethodDialog from "../components/ImageMethodDialog.vue";
import ProjectRepresentativeDialog from "../components/ProjectRepresentativeDialog.vue"; import ProjectRepresentativeDialog from "../components/ProjectRepresentativeDialog.vue";
import ProjectSelectionDialog from "../components/ProjectSelectionDialog.vue";
import QuickNav from "../components/QuickNav.vue"; import QuickNav from "../components/QuickNav.vue";
import { import {
AppString, AppString,
@@ -311,6 +355,7 @@ import {
PROJECT_TIMEOUT_VERY_LONG, PROJECT_TIMEOUT_VERY_LONG,
} from "../constants/notifications"; } from "../constants/notifications";
import { PlanActionClaim } from "../interfaces/claims"; import { PlanActionClaim } from "../interfaces/claims";
import { PlanData } from "../interfaces/records";
import { import {
createEndorserJwtVcFromClaim, createEndorserJwtVcFromClaim,
getHeaders, getHeaders,
@@ -378,6 +423,7 @@ import { logger } from "../utils/logger";
components: { components: {
EntityIcon, EntityIcon,
ImageMethodDialog, ImageMethodDialog,
ProjectSelectionDialog,
ProjectRepresentativeDialog, ProjectRepresentativeDialog,
LMap, LMap,
LMarker, LMarker,
@@ -429,6 +475,8 @@ export default class NewEditProjectView extends Vue {
latitude = 0; latitude = 0;
longitude = 0; longitude = 0;
numAccounts = 0; numAccounts = 0;
parentProjectHandleId = "";
parentProjectName = "";
projectId = ""; projectId = "";
projectIssuerDid = ""; projectIssuerDid = "";
sendToTrustroots = false; sendToTrustroots = false;
@@ -510,6 +558,10 @@ export default class NewEditProjectView extends Vue {
); );
} }
} }
if (this.fullClaim?.fulfills?.identifier) {
this.parentProjectHandleId = this.fullClaim.fulfills.identifier;
this.loadParentProjectName(this.parentProjectHandleId);
}
if (this.fullClaim.startTime) { if (this.fullClaim.startTime) {
const localDateTime = DateTime.fromISO( const localDateTime = DateTime.fromISO(
this.fullClaim.startTime as string, this.fullClaim.startTime as string,
@@ -623,6 +675,14 @@ export default class NewEditProjectView extends Vue {
} else { } else {
delete vcClaim.agent; delete vcClaim.agent;
} }
if (this.parentProjectHandleId) {
vcClaim.fulfills = {
"@type": "PlanAction",
identifier: this.parentProjectHandleId,
};
} else {
delete vcClaim.fulfills;
}
if (this.imageUrl) { if (this.imageUrl) {
vcClaim.image = this.imageUrl; vcClaim.image = this.imageUrl;
} else { } else {
@@ -1075,5 +1135,33 @@ export default class NewEditProjectView extends Vue {
unsetRepresentative(): void { unsetRepresentative(): void {
this.agentDid = ""; this.agentDid = "";
} }
openParentProjectDialog(): void {
(this.$refs.parentProjectDialog as ProjectSelectionDialog).open();
}
handleParentProjectSelected(project: PlanData): void {
this.parentProjectHandleId = project.handleId;
this.parentProjectName = project.name;
}
unsetParentProject(): void {
this.parentProjectHandleId = "";
this.parentProjectName = "";
}
private async loadParentProjectName(handleId: string): Promise<void> {
try {
const url =
this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(handleId);
const headers = await getHeaders(this.activeDid);
const resp = await this.axios.get(url, { headers });
if (resp.status === 200 && resp.data?.claim?.name) {
this.parentProjectName = resp.data.claim.name;
}
} catch {
// Parent project name will remain empty
}
}
} }
</script> </script>

View File

@@ -267,7 +267,7 @@
</form> </form>
</div> </div>
<MeetingProjectDialog <ProjectSelectionDialog
ref="meetingProjectDialog" ref="meetingProjectDialog"
:active-did="activeDid" :active-did="activeDid"
:all-my-dids="allMyDids" :all-my-dids="allMyDids"
@@ -585,7 +585,7 @@ import TopMessage from "../components/TopMessage.vue";
import MeetingMembersList from "../components/MeetingMembersList.vue"; import MeetingMembersList from "../components/MeetingMembersList.vue";
import MeetingMemberMatch from "../components/MeetingMemberMatch.vue"; import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
import MeetingExclusionGroups from "../components/MeetingExclusionGroups.vue"; import MeetingExclusionGroups from "../components/MeetingExclusionGroups.vue";
import MeetingProjectDialog from "../components/MeetingProjectDialog.vue"; import ProjectSelectionDialog from "../components/ProjectSelectionDialog.vue";
import ProjectIcon from "../components/ProjectIcon.vue"; import ProjectIcon from "../components/ProjectIcon.vue";
import { import {
errorStringForLog, errorStringForLog,
@@ -637,7 +637,7 @@ interface MeetingSetupInputs {
MeetingMembersList, MeetingMembersList,
MeetingMemberMatch, MeetingMemberMatch,
MeetingExclusionGroups, MeetingExclusionGroups,
MeetingProjectDialog, ProjectSelectionDialog,
ProjectIcon, ProjectIcon,
}, },
mixins: [PlatformServiceMixin], mixins: [PlatformServiceMixin],
@@ -1468,7 +1468,7 @@ export default class OnboardMeetingView extends Vue {
* Open the project link selection dialog * Open the project link selection dialog
*/ */
openProjectLinkDialog(): void { openProjectLinkDialog(): void {
(this.$refs.meetingProjectDialog as MeetingProjectDialog).open(); (this.$refs.meetingProjectDialog as ProjectSelectionDialog).open();
} }
/** /**

View File

@@ -193,7 +193,7 @@
class="bg-slate-100 px-4 py-3 rounded-md" class="bg-slate-100 px-4 py-3 rounded-md"
> >
<h3 class="text-sm uppercase font-semibold mt-3"> <h3 class="text-sm uppercase font-semibold mt-3">
Projects That Contribute To This These Projects Are Part Of This
</h3> </h3>
<!-- <!--
centering because long, wrapped project names didn't left align with blank centering because long, wrapped project names didn't left align with blank
@@ -218,7 +218,7 @@
<div> <div>
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md"> <div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3"> <h3 class="text-sm uppercase font-semibold mb-3">
Projects Getting Contributions From This This Project Is Part Of These
</h3> </h3>
<!-- <!--
centering because long, wrapped project names didn't left align with blank centering because long, wrapped project names didn't left align with blank