forked from trent_larson/crowd-funder-for-time-pwa
feat: disallow selection of a person or project if it's already selected on the other side (giver/receiver)
This commit is contained in:
@@ -155,6 +155,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
|
|||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
|
:conflicted="isProjectConflicted(project.handleId)"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@project-selected="handleProjectSelected"
|
@project-selected="handleProjectSelected"
|
||||||
@@ -175,6 +176,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
|
|||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
|
:conflicted="isProjectConflicted(project.handleId)"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@project-selected="handleProjectSelected"
|
@project-selected="handleProjectSelected"
|
||||||
@@ -190,6 +192,7 @@ projects, and special entities with selection. * * @author Matthew Raymer */
|
|||||||
:active-did="activeDid"
|
:active-did="activeDid"
|
||||||
:all-my-dids="allMyDids"
|
:all-my-dids="allMyDids"
|
||||||
:all-contacts="allContacts"
|
:all-contacts="allContacts"
|
||||||
|
:conflicted="isProjectConflicted(project.handleId)"
|
||||||
:notify="notify"
|
:notify="notify"
|
||||||
:conflict-context="conflictContext"
|
:conflict-context="conflictContext"
|
||||||
@project-selected="handleProjectSelected"
|
@project-selected="handleProjectSelected"
|
||||||
@@ -555,6 +558,13 @@ export default class EntityGrid extends Vue {
|
|||||||
return this.conflictChecker(did);
|
return this.conflictChecker(did);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a project handleId is conflicted
|
||||||
|
*/
|
||||||
|
isProjectConflicted(handleId: string): boolean {
|
||||||
|
return this.conflictChecker(handleId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle person selection from PersonCard
|
* Handle person selection from PersonCard
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -179,22 +179,56 @@ export default class GiftedDialog extends Vue {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computed property to check if a contact would create a conflict when selected
|
// Computed property to check if current selection would create a project conflict
|
||||||
wouldCreateConflict(contactDid: string) {
|
get hasProjectConflict() {
|
||||||
// Only check for conflicts when both entities are persons
|
// Only check for conflicts when both entities are projects
|
||||||
if (
|
if (
|
||||||
this.currentGiverEntityType !== "person" ||
|
this.currentGiverEntityType !== "project" ||
|
||||||
this.currentRecipientEntityType !== "person"
|
this.currentRecipientEntityType !== "project"
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if giver and recipient are the same project
|
||||||
|
if (
|
||||||
|
this.giver?.handleId &&
|
||||||
|
this.receiver?.handleId &&
|
||||||
|
this.giver.handleId === this.receiver.handleId
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computed property to check if a contact or project would create a conflict when selected
|
||||||
|
wouldCreateConflict(identifier: string) {
|
||||||
|
// Check for person conflicts when both entities are persons
|
||||||
|
if (
|
||||||
|
this.currentGiverEntityType === "person" &&
|
||||||
|
this.currentRecipientEntityType === "person"
|
||||||
|
) {
|
||||||
if (this.stepType === "giver") {
|
if (this.stepType === "giver") {
|
||||||
// If selecting as giver, check if it conflicts with current recipient
|
// If selecting as giver, check if it conflicts with current recipient
|
||||||
return this.receiver?.did === contactDid;
|
return this.receiver?.did === identifier;
|
||||||
} else if (this.stepType === "recipient") {
|
} else if (this.stepType === "recipient") {
|
||||||
// If selecting as recipient, check if it conflicts with current giver
|
// If selecting as recipient, check if it conflicts with current giver
|
||||||
return this.giver?.did === contactDid;
|
return this.giver?.did === identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for project conflicts when both entities are projects
|
||||||
|
if (
|
||||||
|
this.currentGiverEntityType === "project" &&
|
||||||
|
this.currentRecipientEntityType === "project"
|
||||||
|
) {
|
||||||
|
if (this.stepType === "giver") {
|
||||||
|
// If selecting as giver, check if it conflicts with current recipient
|
||||||
|
return this.receiver?.handleId === identifier;
|
||||||
|
} else if (this.stepType === "recipient") {
|
||||||
|
// If selecting as recipient, check if it conflicts with current giver
|
||||||
|
return this.giver?.handleId === identifier;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -363,6 +397,15 @@ export default class GiftedDialog extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for project conflict
|
||||||
|
if (this.hasProjectConflict) {
|
||||||
|
this.safeNotify.error(
|
||||||
|
"You cannot select the same project as both giver and recipient.",
|
||||||
|
TIMEOUTS.STANDARD,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
this.safeNotify.toast(
|
this.safeNotify.toast(
|
||||||
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message,
|
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE.message,
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/** * ProjectCard.vue - Individual project display component * * Extracted from
|
/** * ProjectCard.vue - Individual project display component * * Extracted from
|
||||||
GiftedDialog.vue to handle project entity display * with selection states and
|
GiftedDialog.vue to handle project entity display * with selection states,
|
||||||
issuer information. * * @author Matthew Raymer */
|
conflict detection, and issuer information. * * @author Matthew Raymer */
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li :class="cardClasses" @click="handleClick">
|
||||||
class="flex items-center gap-2 px-2 py-1.5 border-b border-slate-300 hover:bg-slate-50 cursor-pointer"
|
|
||||||
@click="handleClick"
|
|
||||||
>
|
|
||||||
<ProjectIcon
|
<ProjectIcon
|
||||||
:entity-id="project.handleId"
|
:entity-id="project.handleId"
|
||||||
:icon-size="30"
|
:icon-size="30"
|
||||||
@@ -14,8 +11,8 @@ issuer information. * * @author Matthew Raymer */
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<h3 class="text-sm font-semibold truncate">
|
<h3 :class="nameClasses">
|
||||||
{{ project.name || unnamedProject }}
|
{{ displayName }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="text-xs text-slate-500 truncate">
|
<div class="text-xs text-slate-500 truncate">
|
||||||
@@ -33,6 +30,7 @@ import { PlanData } from "../interfaces/records";
|
|||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { didInfo } from "../libs/endorserServer";
|
import { didInfo } from "../libs/endorserServer";
|
||||||
import { UNNAMED_PROJECT } from "@/constants/entities";
|
import { UNNAMED_PROJECT } from "@/constants/entities";
|
||||||
|
import { NotificationIface } from "../constants/app";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProjectCard - Displays a project entity with selection capability
|
* ProjectCard - Displays a project entity with selection capability
|
||||||
@@ -42,6 +40,8 @@ import { UNNAMED_PROJECT } from "@/constants/entities";
|
|||||||
* - Displays project name and issuer information
|
* - Displays project name and issuer information
|
||||||
* - Handles click events for selection
|
* - Handles click events for selection
|
||||||
* - Shows issuer name using didInfo utility
|
* - Shows issuer name using didInfo utility
|
||||||
|
* - Selection states (selectable, conflicted, disabled)
|
||||||
|
* - Warning notifications for conflicted entities
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -65,6 +65,18 @@ export default class ProjectCard extends Vue {
|
|||||||
@Prop({ required: true })
|
@Prop({ required: true })
|
||||||
allContacts!: Contact[];
|
allContacts!: Contact[];
|
||||||
|
|
||||||
|
/** Whether this project would create a conflict if selected */
|
||||||
|
@Prop({ default: false })
|
||||||
|
conflicted!: boolean;
|
||||||
|
|
||||||
|
/** Notification function from parent component */
|
||||||
|
@Prop()
|
||||||
|
notify?: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
/** Context for conflict messages (e.g., "giver", "recipient") */
|
||||||
|
@Prop({ default: "other party" })
|
||||||
|
conflictContext!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the unnamed project constant
|
* Get the unnamed project constant
|
||||||
*/
|
*/
|
||||||
@@ -72,6 +84,51 @@ export default class ProjectCard extends Vue {
|
|||||||
return UNNAMED_PROJECT;
|
return UNNAMED_PROJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the card
|
||||||
|
*/
|
||||||
|
get cardClasses(): string {
|
||||||
|
const baseCardClasses =
|
||||||
|
"flex items-center gap-2 px-2 py-1.5 border-b border-slate-300";
|
||||||
|
|
||||||
|
if (this.conflicted) {
|
||||||
|
return `${baseCardClasses} *:opacity-50 cursor-not-allowed`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${baseCardClasses} cursor-pointer hover:bg-slate-50`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed CSS classes for the project name
|
||||||
|
*/
|
||||||
|
get nameClasses(): string {
|
||||||
|
const baseNameClasses = "text-sm font-semibold truncate";
|
||||||
|
|
||||||
|
if (this.conflicted) {
|
||||||
|
return `${baseNameClasses} text-slate-500`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add italic styling for entities without set names
|
||||||
|
if (!this.project.name) {
|
||||||
|
return `${baseNameClasses} italic text-slate-500`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseNameClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed display name for the project
|
||||||
|
*/
|
||||||
|
get displayName(): string {
|
||||||
|
// If the project has a set name, use that name
|
||||||
|
if (this.project.name) {
|
||||||
|
return this.project.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the project does not have a set name
|
||||||
|
return this.unnamedProject;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computed display name for the project issuer
|
* Computed display name for the project issuer
|
||||||
*/
|
*/
|
||||||
@@ -85,10 +142,23 @@ export default class ProjectCard extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle card click - emit project selection
|
* Handle card click - emit if not conflicted, show warning if conflicted
|
||||||
*/
|
*/
|
||||||
handleClick(): void {
|
handleClick(): void {
|
||||||
|
if (!this.conflicted) {
|
||||||
this.emitProjectSelected(this.project);
|
this.emitProjectSelected(this.project);
|
||||||
|
} else if (this.notify) {
|
||||||
|
// Show warning notification for conflicted entity
|
||||||
|
this.notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Cannot Select",
|
||||||
|
text: `You cannot select "${this.displayName}" because it is already selected as the ${this.conflictContext}.`,
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit methods using @Emit decorator
|
// Emit methods using @Emit decorator
|
||||||
|
|||||||
Reference in New Issue
Block a user