forked from trent_larson/crowd-funder-for-time-pwa
feat(meetings): add project selection dialog for meeting setup
Replace Project Link text input with interactive selection dialog using new MeetingProjectDialog component. Dialog displays user's projects with icons and issuer information, following the same pattern as ProjectRepresentativeDialog. - Create MeetingProjectDialog with EntityGrid integration - Add clickable project field with icon, name, and issuer display - Load projects from /api/v2/report/plansByIssuer endpoint - Show issuer name instead of handleId for better UX - Refactor loadProjects to remove unused rowId field
This commit is contained in:
@@ -186,16 +186,59 @@
|
||||
<div>
|
||||
<label
|
||||
for="projectLink"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>Project Link</label
|
||||
>
|
||||
<input
|
||||
id="projectLink"
|
||||
v-model="newOrUpdatedMeetingInputs.projectLink"
|
||||
type="text"
|
||||
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Project ID"
|
||||
/>
|
||||
<div class="w-full flex items-stretch">
|
||||
<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="openProjectLinkDialog"
|
||||
>
|
||||
<div>
|
||||
<ProjectIcon
|
||||
v-if="selectedProject"
|
||||
:entity-id="selectedProject.handleId"
|
||||
:icon-size="30"
|
||||
:image-url="selectedProject.image"
|
||||
class="!size-[2rem] shrink-0 border border-slate-300 bg-white overflow-hidden rounded-full"
|
||||
/>
|
||||
<font-awesome
|
||||
v-else
|
||||
icon="folder-open"
|
||||
class="text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
<div
|
||||
:class="{
|
||||
'text-sm font-semibold': selectedProject,
|
||||
'text-slate-400': !selectedProject,
|
||||
}"
|
||||
class="truncate"
|
||||
>
|
||||
{{
|
||||
selectedProject
|
||||
? selectedProject.name || "Unnamed Project"
|
||||
: "Select Project…"
|
||||
}}
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedProject"
|
||||
class="text-xs text-slate-500 truncate"
|
||||
>
|
||||
<font-awesome icon="user" class="text-slate-400" />
|
||||
{{ selectedProjectIssuerName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="selectedProject"
|
||||
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="unsetProjectLink"
|
||||
>
|
||||
<font-awesome icon="trash-can" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -224,6 +267,16 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<MeetingProjectDialog
|
||||
ref="meetingProjectDialog"
|
||||
:all-projects="allProjects"
|
||||
:active-did="activeDid"
|
||||
:all-my-dids="allMyDids"
|
||||
:all-contacts="allContacts"
|
||||
:notify="$notify"
|
||||
@assign="handleProjectLinkAssigned"
|
||||
/>
|
||||
|
||||
<!-- Members Section -->
|
||||
<div
|
||||
v-if="!isLoading && currentMeeting != null && !!currentMeeting.password"
|
||||
@@ -292,10 +345,13 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import TopMessage from "../components/TopMessage.vue";
|
||||
import MembersList from "../components/MembersList.vue";
|
||||
import MeetingProjectDialog from "../components/MeetingProjectDialog.vue";
|
||||
import ProjectIcon from "../components/ProjectIcon.vue";
|
||||
import {
|
||||
errorStringForLog,
|
||||
getHeaders,
|
||||
serverMessageForUser,
|
||||
didInfo,
|
||||
} from "../libs/endorserServer";
|
||||
import { encryptMessage } from "../libs/crypto";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
@@ -309,6 +365,8 @@ import {
|
||||
NOTIFY_MEETING_DELETED,
|
||||
NOTIFY_MEETING_LINK_COPIED,
|
||||
} from "@/constants/notifications";
|
||||
import { PlanData } from "../interfaces/records";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
interface ServerMeeting {
|
||||
groupId: number; // from the server
|
||||
name: string; // to & from the server
|
||||
@@ -331,6 +389,8 @@ interface MeetingSetupInputs {
|
||||
QuickNav,
|
||||
TopMessage,
|
||||
MembersList,
|
||||
MeetingProjectDialog,
|
||||
ProjectIcon,
|
||||
},
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
@@ -354,6 +414,9 @@ export default class OnboardMeetingView extends Vue {
|
||||
isRegistered = false;
|
||||
showDeleteConfirm = false;
|
||||
fullName = "";
|
||||
allProjects: PlanData[] = [];
|
||||
allContacts: Contact[] = [];
|
||||
allMyDids: string[] = [];
|
||||
get minDateTime() {
|
||||
const now = new Date();
|
||||
now.setMinutes(now.getMinutes() + 5); // Set minimum 5 minutes in the future
|
||||
@@ -370,6 +433,15 @@ export default class OnboardMeetingView extends Vue {
|
||||
this.fullName = settings?.firstName || "";
|
||||
this.isRegistered = !!settings?.isRegistered;
|
||||
|
||||
// Load contacts and DIDs for dialog
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.allContacts = await (this as any).$contactsByDateAdded();
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.allMyDids = await (this as any).$getAllAccountDids();
|
||||
|
||||
// Load projects
|
||||
await this.loadProjects();
|
||||
|
||||
await this.fetchCurrentMeeting();
|
||||
this.isLoading = false;
|
||||
}
|
||||
@@ -710,5 +782,97 @@ export default class OnboardMeetingView extends Vue {
|
||||
this.notify.error("Failed to copy meeting link to clipboard.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load projects from the API
|
||||
*/
|
||||
async loadProjects() {
|
||||
try {
|
||||
const headers = await getHeaders(this.activeDid);
|
||||
const url = `${this.apiServer}/api/v2/report/plansByIssuer`;
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
|
||||
if (resp.status === 200 && resp.data.data) {
|
||||
this.allProjects = resp.data.data.map(
|
||||
(plan: {
|
||||
name: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
handleId: string;
|
||||
issuerDid: string;
|
||||
}) => ({
|
||||
name: plan.name,
|
||||
description: plan.description,
|
||||
image: plan.image,
|
||||
handleId: plan.handleId,
|
||||
issuerDid: plan.issuerDid,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.$logAndConsole(
|
||||
"Error loading projects: " + errorStringForLog(error),
|
||||
true,
|
||||
);
|
||||
// Don't show error to user - just leave projects empty
|
||||
this.allProjects = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed property for selected project
|
||||
* Derives the project from projectLink by finding it in allProjects
|
||||
*/
|
||||
get selectedProject(): PlanData | null {
|
||||
if (!this.newOrUpdatedMeetingInputs?.projectLink) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
this.allProjects.find(
|
||||
(p) => p.handleId === this.newOrUpdatedMeetingInputs?.projectLink,
|
||||
) || null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed property for selected project issuer display name
|
||||
* Uses didInfo to format the issuer name similar to ProjectCard
|
||||
*/
|
||||
get selectedProjectIssuerName(): string {
|
||||
if (!this.selectedProject) {
|
||||
return "";
|
||||
}
|
||||
return didInfo(
|
||||
this.selectedProject.issuerDid,
|
||||
this.activeDid,
|
||||
this.allMyDids,
|
||||
this.allContacts,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the project link selection dialog
|
||||
*/
|
||||
openProjectLinkDialog(): void {
|
||||
(this.$refs.meetingProjectDialog as MeetingProjectDialog).open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle project assignment from dialog
|
||||
*/
|
||||
handleProjectLinkAssigned(project: PlanData): void {
|
||||
if (this.newOrUpdatedMeetingInputs) {
|
||||
this.newOrUpdatedMeetingInputs.projectLink = project.handleId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the project link and revert to initial state
|
||||
*/
|
||||
unsetProjectLink(): void {
|
||||
if (this.newOrUpdatedMeetingInputs) {
|
||||
this.newOrUpdatedMeetingInputs.projectLink = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user