Skip to content

ComplaintPreview ​

The right-hand pane of the inbox: ComplaintHeader over ComplaintThread, or an empty state when nothing is selected. It is a relay — it renders the two children and forwards their setStatus / send events untouched.

vue
<script setup lang="ts">
import RyderComplaintPreview from "@opeolluwa/ryder/components/complaints/ComplaintPreview.vue"
</script>

<template>
  <RyderComplaintPreview
    :row="selectedRow"
    :replies="replies"
    :self="staff"
    :counterpart="customer"
    :sending="sending"
    :sent-count="sentCount"
    @set-status="setStatus"
    @send="sendReply"
  />
</template>

Examples ​

vue
<script setup lang="ts">
import { ref } from "vue";
import ComplaintPreview from "../../../src/components/complaints/ComplaintPreview.vue";
import {
  counterpart,
  replies as seed,
  row,
  self,
} from "./fixtures";
import type {
  ComplaintReply,
  ComplaintRow,
  ComplaintWritableStatus,
} from "../../../src/types/complaints";

const selected = ref<ComplaintRow | null>({ ...row });
const thread = ref<ComplaintReply[]>([...seed]);
const sending = ref(false);
const sentCount = ref(0);
const lastSend = ref("—");
const lastStatus = ref<ComplaintWritableStatus | null>(null);

async function onSend(body: string) {
  lastSend.value = body;
  sending.value = true;

  await new Promise((resolve) => setTimeout(resolve, 400));

  thread.value = [
    ...thread.value,
    {
      identifier: `RPL-${Date.now()}`,
      complaintIdentifier: selected.value?.complaint.identifier ?? row.complaint.identifier,
      body,
      senderEmail: self.email ?? "support@ryder.example",
      createdAt: new Date().toISOString(),
      updatedAt: null,
    },
  ];

  sending.value = false;
  sentCount.value += 1;
}

function onSetStatus(status: ComplaintWritableStatus) {
  lastStatus.value = status;

  if (selected.value) {
    selected.value = {
      ...selected.value,
      complaint: { ...selected.value.complaint, status },
    };
  }
}
</script>

<template>
  <div>
    <div class="flex h-[32rem]">
      <ComplaintPreview
        :row="selected"
        :replies="thread"
        :self="self"
        :counterpart="counterpart"
        :sending="sending"
        :sent-count="sentCount"
        @send="onSend"
        @set-status="onSetStatus"
      />
    </div>

    <div class="mt-3 flex flex-wrap items-center gap-3">
      <button
        type="button"
        class="rounded-lg border border-gray-200 px-3 py-1.5 text-xs dark:border-white/10"
        @click="selected = { ...row }"
      >
        Select complaint
      </button>
      <button
        type="button"
        class="rounded-lg border border-gray-200 px-3 py-1.5 text-xs dark:border-white/10"
        @click="selected = null"
      >
        Clear selection
      </button>
      <span class="text-xs text-gray-500 dark:text-white/40">
        Last send: {{ lastSend }} — last setStatus: {{ lastStatus ?? "—" }}
      </span>
    </div>
  </div>
</template>

Props ​

PropTypeDefaultDescription
rowComplaintRow | null—The selected complaint. null renders the empty state instead of header + thread.
repliesComplaintReply[]—Thread messages, passed straight to the thread.
loadingRepliesbooleanfalseLoader while the thread is fetching.
selfThreadParty—The replier's side of the conversation.
counterpartThreadParty—The other side.
sendingbooleanfalseA reply POST is in flight.
sentCountnumber0Bumped by the parent on each successful send; clears the composer.
statusMenubooleantrueForwarded to the header: offer the status menu.
showCustomerbooleantrueForwarded to the header: show the complainer's identity.

Emits ​

EventPayloadDescription
setStatus[status: ComplaintWritableStatus]Relayed unchanged from the header's status menu.
send[body: string]Relayed unchanged from the thread composer.

Slots ​

None.

Notes ​

  • row === null swaps the whole pane for EmptyState ("No conversation selected") — the header and thread are not rendered at all, so no thread state is mounted for a deselected complaint.
  • No data fetching happens here: replies, sending and sentCount belong to the parent, exactly as in the playground's complaints.vue page, which pairs this with ComplaintList.
  • Because statusMenu / showCustomer only reach the header, a customer build turns both off in one place and the thread is unaffected.