Browse Source
Replace the plain text input for authorized representative with an interactive contact selection interface that provides better UX and maintains data consistency. Changes: - Add ProjectRepresentativeDialog component using EntityGrid for contact selection (excludes "You" and "Unnamed" special entities) - Replace text input with clickable field showing contact icon, name, and DID - Implement conditional UI states: initial "Assign..." placeholder vs assigned representative display with unset button - Refactor selectedRepresentative to computed property derived from agentDid (single source of truth, prevents sync issues) - Inline representativeDisplayName for simplicity - Support changing representative by clicking on assigned field - Support unsetting representative via trash button The new implementation ensures agentDid remains the authoritative state while selectedRepresentative is automatically computed, preventing the previously possible desync when agentDid was set directly (e.g., via the "make original owner an authorized representative" button).pull/219/head
2 changed files with 248 additions and 6 deletions
@ -0,0 +1,128 @@ |
|||
<template> |
|||
<div v-if="visible" class="dialog-overlay"> |
|||
<div class="dialog"> |
|||
<!-- Header --> |
|||
<h2 class="text-lg font-semibold leading-[1.25] mb-4"> |
|||
Select Representative |
|||
</h2> |
|||
|
|||
<!-- EntityGrid for contacts --> |
|||
<EntityGrid |
|||
:entity-type="'people'" |
|||
:entities="allContacts" |
|||
:active-did="activeDid" |
|||
:all-my-dids="allMyDids" |
|||
:all-contacts="allContacts" |
|||
:conflict-checker="conflictChecker" |
|||
:show-you-entity="false" |
|||
:show-unnamed-entity="false" |
|||
:notify="notify" |
|||
:conflict-context="'representative'" |
|||
@entity-selected="handleEntitySelected" |
|||
/> |
|||
|
|||
<!-- Cancel Button --> |
|||
<div class="flex gap-2 mt-4"> |
|||
<button |
|||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-2 rounded-md" |
|||
@click="handleCancel" |
|||
> |
|||
Cancel |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { Component, Prop, Vue, Emit } from "vue-facing-decorator"; |
|||
import EntityGrid from "./EntityGrid.vue"; |
|||
import { Contact } from "../db/tables/contacts"; |
|||
import { NotificationIface } from "../constants/app"; |
|||
|
|||
/** |
|||
* ProjectRepresentativeDialog - Dialog for selecting an authorized representative |
|||
* |
|||
* Features: |
|||
* - EntityGrid integration for contact selection |
|||
* - No special entities (You, Unnamed) |
|||
* - Immediate assignment on contact selection |
|||
* - Cancel button to close without selection |
|||
*/ |
|||
@Component({ |
|||
components: { |
|||
EntityGrid, |
|||
}, |
|||
}) |
|||
export default class ProjectRepresentativeDialog extends Vue { |
|||
/** Whether the dialog is visible */ |
|||
visible = false; |
|||
|
|||
/** Array of available contacts */ |
|||
@Prop({ required: true }) |
|||
allContacts!: Contact[]; |
|||
|
|||
/** Active user's DID */ |
|||
@Prop({ required: true }) |
|||
activeDid!: string; |
|||
|
|||
/** All user's DIDs */ |
|||
@Prop({ required: true }) |
|||
allMyDids!: string[]; |
|||
|
|||
/** Function to check if a person DID would create a conflict */ |
|||
@Prop({ required: true }) |
|||
conflictChecker!: (did: string) => boolean; |
|||
|
|||
/** Notification function from parent component */ |
|||
@Prop() |
|||
notify?: (notification: NotificationIface, timeout?: number) => void; |
|||
|
|||
/** |
|||
* Handle entity selection from EntityGrid |
|||
* Immediately assigns the selected contact and closes the dialog |
|||
*/ |
|||
handleEntitySelected(event: { type: "person" | "project"; data: Contact }) { |
|||
if (event.type === "person") { |
|||
const contact = event.data as Contact; |
|||
this.emitAssign(contact); |
|||
this.close(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Handle cancel button click |
|||
*/ |
|||
handleCancel(): void { |
|||
this.close(); |
|||
} |
|||
|
|||
/** |
|||
* Open the dialog |
|||
*/ |
|||
open(): void { |
|||
this.visible = true; |
|||
} |
|||
|
|||
/** |
|||
* Close the dialog |
|||
*/ |
|||
close(): void { |
|||
this.visible = false; |
|||
} |
|||
|
|||
// Emit methods using @Emit decorator |
|||
|
|||
@Emit("assign") |
|||
emitAssign(contact: Contact): Contact { |
|||
return contact; |
|||
} |
|||
|
|||
@Emit("cancel") |
|||
emitCancel(): void { |
|||
// No return value needed |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped></style> |
|||
Loading…
Reference in new issue