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

View File

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

View File

@@ -114,6 +114,49 @@
@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">
<p v-if="shouldShowOwnershipWarning">
<span class="text-red-500">Beware!</span>
@@ -283,6 +326,7 @@ import { LeafletMouseEvent } from "leaflet";
import EntityIcon from "../components/EntityIcon.vue";
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
import ProjectRepresentativeDialog from "../components/ProjectRepresentativeDialog.vue";
import ProjectSelectionDialog from "../components/ProjectSelectionDialog.vue";
import QuickNav from "../components/QuickNav.vue";
import {
AppString,
@@ -311,6 +355,7 @@ import {
PROJECT_TIMEOUT_VERY_LONG,
} from "../constants/notifications";
import { PlanActionClaim } from "../interfaces/claims";
import { PlanData } from "../interfaces/records";
import {
createEndorserJwtVcFromClaim,
getHeaders,
@@ -378,6 +423,7 @@ import { logger } from "../utils/logger";
components: {
EntityIcon,
ImageMethodDialog,
ProjectSelectionDialog,
ProjectRepresentativeDialog,
LMap,
LMarker,
@@ -429,6 +475,8 @@ export default class NewEditProjectView extends Vue {
latitude = 0;
longitude = 0;
numAccounts = 0;
parentProjectHandleId = "";
parentProjectName = "";
projectId = "";
projectIssuerDid = "";
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) {
const localDateTime = DateTime.fromISO(
this.fullClaim.startTime as string,
@@ -623,6 +675,14 @@ export default class NewEditProjectView extends Vue {
} else {
delete vcClaim.agent;
}
if (this.parentProjectHandleId) {
vcClaim.fulfills = {
"@type": "PlanAction",
identifier: this.parentProjectHandleId,
};
} else {
delete vcClaim.fulfills;
}
if (this.imageUrl) {
vcClaim.image = this.imageUrl;
} else {
@@ -1075,5 +1135,33 @@ export default class NewEditProjectView extends Vue {
unsetRepresentative(): void {
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>

View File

@@ -267,7 +267,7 @@
</form>
</div>
<MeetingProjectDialog
<ProjectSelectionDialog
ref="meetingProjectDialog"
:active-did="activeDid"
:all-my-dids="allMyDids"
@@ -585,7 +585,7 @@ import TopMessage from "../components/TopMessage.vue";
import MeetingMembersList from "../components/MeetingMembersList.vue";
import MeetingMemberMatch from "../components/MeetingMemberMatch.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 {
errorStringForLog,
@@ -637,7 +637,7 @@ interface MeetingSetupInputs {
MeetingMembersList,
MeetingMemberMatch,
MeetingExclusionGroups,
MeetingProjectDialog,
ProjectSelectionDialog,
ProjectIcon,
},
mixins: [PlatformServiceMixin],
@@ -1468,7 +1468,7 @@ export default class OnboardMeetingView extends Vue {
* Open the project link selection dialog
*/
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"
>
<h3 class="text-sm uppercase font-semibold mt-3">
Projects That Contribute To This
These Projects Are Part Of This
</h3>
<!--
centering because long, wrapped project names didn't left align with blank
@@ -218,7 +218,7 @@
<div>
<div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
<h3 class="text-sm uppercase font-semibold mb-3">
Projects Getting Contributions From This
This Project Is Part Of These
</h3>
<!--
centering because long, wrapped project names didn't left align with blank