Browse Source

Feature: notify on click why entity is disabled

pull/142/head
Jose Olarte III 22 hours ago
parent
commit
b57f7e4dd7
  1. 34
      src/components/EntityGrid.vue
  2. 19
      src/components/EntitySelectionStep.vue
  3. 1
      src/components/GiftedDialog.vue
  4. 23
      src/components/PersonCard.vue
  5. 33
      src/components/SpecialEntityCard.vue

34
src/components/EntityGrid.vue

@ -14,6 +14,8 @@ projects, and special entities with selection. * * @author Matthew Raymer */
:selectable="youSelectable" :selectable="youSelectable"
:conflicted="youConflicted" :conflicted="youConflicted"
:entity-data="youEntityData" :entity-data="youEntityData"
:notify="notify"
:conflict-context="conflictContext"
@entity-selected="handleEntitySelected" @entity-selected="handleEntitySelected"
/> />
@ -23,6 +25,8 @@ projects, and special entities with selection. * * @author Matthew Raymer */
label="Unnamed" label="Unnamed"
icon="circle-question" icon="circle-question"
:entity-data="unnamedEntityData" :entity-data="unnamedEntityData"
:notify="notify"
:conflict-context="conflictContext"
@entity-selected="handleEntitySelected" @entity-selected="handleEntitySelected"
/> />
</template> </template>
@ -38,23 +42,27 @@ projects, and special entities with selection. * * @author Matthew Raymer */
<!-- Entity cards (people or projects) --> <!-- Entity cards (people or projects) -->
<template v-if="entityType === 'people'"> <template v-if="entityType === 'people'">
<PersonCard <PersonCard
v-for="person in displayedEntities" v-for="person in displayedEntities as Contact[]"
:key="person.did" :key="person.did"
:person="person" :person="person"
:conflicted="isPersonConflicted(person.did)" :conflicted="isPersonConflicted(person.did)"
:show-time-icon="true" :show-time-icon="true"
:notify="notify"
:conflict-context="conflictContext"
@person-selected="handlePersonSelected" @person-selected="handlePersonSelected"
/> />
</template> </template>
<template v-else-if="entityType === 'projects'"> <template v-else-if="entityType === 'projects'">
<ProjectCard <ProjectCard
v-for="project in displayedEntities" v-for="project in displayedEntities as PlanData[]"
:key="project.handleId" :key="project.handleId"
:project="project" :project="project"
:active-did="activeDid" :active-did="activeDid"
:all-my-dids="allMyDids" :all-my-dids="allMyDids"
:all-contacts="allContacts" :all-contacts="allContacts"
:notify="notify"
:conflict-context="conflictContext"
@project-selected="handleProjectSelected" @project-selected="handleProjectSelected"
/> />
</template> </template>
@ -77,6 +85,7 @@ import SpecialEntityCard from "./SpecialEntityCard.vue";
import ShowAllCard from "./ShowAllCard.vue"; import ShowAllCard from "./ShowAllCard.vue";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import { PlanData } from "../interfaces/records"; import { PlanData } from "../interfaces/records";
import { NotificationIface } from "../constants/app";
/** /**
* EntityGrid - Unified grid layout for displaying people or projects * EntityGrid - Unified grid layout for displaying people or projects
@ -88,6 +97,7 @@ import { PlanData } from "../interfaces/records";
* - Empty state messaging * - Empty state messaging
* - Show All navigation * - Show All navigation
* - Event delegation for entity selection * - Event delegation for entity selection
* - Warning notifications for conflicted entities
*/ */
@Component({ @Component({
components: { components: {
@ -142,6 +152,14 @@ export default class EntityGrid extends Vue {
@Prop({ default: () => ({}) }) @Prop({ default: () => ({}) })
showAllQueryParams!: Record<string, string>; showAllQueryParams!: Record<string, string>;
/** 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;
/** /**
* Computed CSS classes for the grid layout * Computed CSS classes for the grid layout
*/ */
@ -241,7 +259,7 @@ export default class EntityGrid extends Vue {
handleEntitySelected(event: { handleEntitySelected(event: {
type: string; type: string;
entityType: string; entityType: string;
data: any; data: { did?: string; name: string };
}): void { }): void {
this.emitEntitySelected({ this.emitEntitySelected({
type: "special", type: "special",
@ -253,7 +271,15 @@ export default class EntityGrid extends Vue {
// Emit methods using @Emit decorator // Emit methods using @Emit decorator
@Emit("entity-selected") @Emit("entity-selected")
emitEntitySelected(data: any): any { emitEntitySelected(data: {
type: "person" | "project" | "special";
entityType?: string;
data: Contact | PlanData | { did?: string; name: string };
}): {
type: "person" | "project" | "special";
entityType?: string;
data: Contact | PlanData | { did?: string; name: string };
} {
return data; return data;
} }
} }

19
src/components/EntitySelectionStep.vue

@ -19,6 +19,8 @@ with dynamic labeling and grid display. * * @author Matthew Raymer */
:you-selectable="youSelectable" :you-selectable="youSelectable"
:show-all-route="showAllRoute" :show-all-route="showAllRoute"
:show-all-query-params="showAllQueryParams" :show-all-query-params="showAllQueryParams"
:notify="notify"
:conflict-context="conflictContext"
@entity-selected="handleEntitySelected" @entity-selected="handleEntitySelected"
/> />
@ -36,6 +38,7 @@ import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
import EntityGrid from "./EntityGrid.vue"; import EntityGrid from "./EntityGrid.vue";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import { PlanData } from "../interfaces/records"; import { PlanData } from "../interfaces/records";
import { NotificationIface } from "../constants/app";
/** /**
* Entity data interface for giver/receiver * Entity data interface for giver/receiver
@ -67,6 +70,7 @@ interface EntitySelectionEvent {
* - Show All navigation with context preservation * - Show All navigation with context preservation
* - Cancel functionality * - Cancel functionality
* - Event delegation for entity selection * - Event delegation for entity selection
* - Warning notifications for conflicted entities
*/ */
@Component({ @Component({
components: { components: {
@ -130,6 +134,10 @@ export default class EntitySelectionStep extends Vue {
@Prop() @Prop()
receiver?: EntityData | null; receiver?: EntityData | null;
/** Notification function from parent component */
@Prop()
notify?: (notification: NotificationIface, timeout?: number) => void;
/** /**
* Computed step label based on context * Computed step label based on context
*/ */
@ -143,6 +151,17 @@ export default class EntitySelectionStep extends Vue {
} }
} }
/**
* Computed conflict context for better error messages
*/
get conflictContext(): string {
if (this.stepType === "giver") {
return "recipient";
} else {
return "giver";
}
}
/** /**
* Whether to show projects in the grid * Whether to show projects in the grid
*/ */

1
src/components/GiftedDialog.vue

@ -18,6 +18,7 @@
:to-project-id="toProjectId" :to-project-id="toProjectId"
:giver="giver" :giver="giver"
:receiver="receiver" :receiver="receiver"
:notify="$notify"
@entity-selected="handleEntitySelected" @entity-selected="handleEntitySelected"
@cancel="cancel" @cancel="cancel"
/> />

23
src/components/PersonCard.vue

@ -34,6 +34,7 @@ conflict detection. * * @author Matthew Raymer */
import { Component, Prop, Vue, Emit } from "vue-facing-decorator"; import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
import EntityIcon from "./EntityIcon.vue"; import EntityIcon from "./EntityIcon.vue";
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import { NotificationIface } from "../constants/app";
/** /**
* PersonCard - Individual person display with selection capability * PersonCard - Individual person display with selection capability
@ -44,6 +45,7 @@ import { Contact } from "../db/tables/contacts";
* - Time icon overlay for contacts * - Time icon overlay for contacts
* - Click event handling * - Click event handling
* - Emits click events for parent handling * - Emits click events for parent handling
* - Warning notifications for conflicted entities
*/ */
@Component({ @Component({
components: { components: {
@ -67,6 +69,14 @@ export default class PersonCard extends Vue {
@Prop({ default: false }) @Prop({ default: false })
showTimeIcon!: boolean; showTimeIcon!: 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;
/** /**
* Computed CSS classes for the card * Computed CSS classes for the card
*/ */
@ -92,11 +102,22 @@ export default class PersonCard extends Vue {
} }
/** /**
* Handle card click - only emit if selectable and not conflicted * Handle card click - emit if selectable and not conflicted, show warning if conflicted
*/ */
handleClick(): void { handleClick(): void {
if (this.selectable && !this.conflicted) { if (this.selectable && !this.conflicted) {
this.emitPersonSelected(this.person); this.emitPersonSelected(this.person);
} else if (this.conflicted && this.notify) {
// Show warning notification for conflicted entity
this.notify(
{
group: "alert",
type: "warning",
title: "Cannot Select",
text: `You cannot select "${this.person.name || this.person.did || "Unnamed"}" because they are already selected as the ${this.conflictContext}.`,
},
3000,
);
} }
} }

33
src/components/SpecialEntityCard.vue

@ -13,6 +13,7 @@ conflict detection and selection capability. * * @author Matthew Raymer */
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-facing-decorator"; import { Component, Prop, Vue } from "vue-facing-decorator";
import { Emit } from "vue-facing-decorator"; import { Emit } from "vue-facing-decorator";
import { NotificationIface } from "../constants/app";
/** /**
* SpecialEntityCard - Displays special entities with selection capability * SpecialEntityCard - Displays special entities with selection capability
@ -23,6 +24,7 @@ import { Emit } from "vue-facing-decorator";
* - Handles conflict states and selection * - Handles conflict states and selection
* - Emits selection events with entity data * - Emits selection events with entity data
* - Configurable styling based on entity type * - Configurable styling based on entity type
* - Warning notifications for conflicted entities
*/ */
@Component({ @Component({
emits: ["entity-selected"], emits: ["entity-selected"],
@ -52,6 +54,14 @@ export default class SpecialEntityCard extends Vue {
@Prop({ required: true }) @Prop({ required: true })
entityData!: { did?: string; name: string }; entityData!: { did?: string; name: string };
/** 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;
/** /**
* Computed CSS classes for the card container * Computed CSS classes for the card container
*/ */
@ -109,7 +119,7 @@ export default class SpecialEntityCard extends Vue {
} }
/** /**
* Handle card click - only emit if selectable and not conflicted * Handle card click - emit if selectable and not conflicted, show warning if conflicted
*/ */
handleClick(): void { handleClick(): void {
if (this.selectable && !this.conflicted) { if (this.selectable && !this.conflicted) {
@ -118,13 +128,32 @@ export default class SpecialEntityCard extends Vue {
entityType: this.entityType, entityType: this.entityType,
data: this.entityData, data: this.entityData,
}); });
} else if (this.conflicted && this.notify) {
// Show warning notification for conflicted entity
this.notify(
{
group: "alert",
type: "warning",
title: "Cannot Select",
text: `You cannot select "${this.label}" because you are already selected as the ${this.conflictContext}.`,
},
3000,
);
} }
} }
// Emit methods using @Emit decorator // Emit methods using @Emit decorator
@Emit("entity-selected") @Emit("entity-selected")
emitEntitySelected(data: any): any { emitEntitySelected(data: {
type: string;
entityType: string;
data: { did?: string; name: string };
}): {
type: string;
entityType: string;
data: { did?: string; name: string };
} {
return data; return data;
} }
} }

Loading…
Cancel
Save