fix(call): show & persist 'called' status
- mapOrderGroupRowToDeliveryGroup dropped called/called_at/called_comment fields (they were selected but never mapped) so the panel always read 'not called' and freshly saved calls appeared to vanish. - CalledPanel: one-tap 'Звонили' marks called with now; app-styled date/time dropdowns (CallDateTime) default to current moment for fast entry; comment edit form pre-filled. - OrderDetailPanel: forward real onRefreshOrder (was a silent no-op) so the card refreshes from DB after a save.
This commit is contained in:
parent
f5a06188a2
commit
6ca8bd545e
|
|
@ -0,0 +1,149 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
// ─── helpers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const pad = (n) => String(n).padStart(2, "0");
|
||||||
|
|
||||||
|
export const toDateInputValue = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||||
|
export const toTimeInputValue = (d) => `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||||
|
|
||||||
|
const formatRuDate = (dateStr) => {
|
||||||
|
if (!dateStr) return "";
|
||||||
|
const [y, m, d] = dateStr.split("-").map(Number);
|
||||||
|
if (!y || !m || !d) return dateStr;
|
||||||
|
const dt = new Date(y, m - 1, d);
|
||||||
|
const weekday = dt.toLocaleDateString("ru-RU", { weekday: "short" });
|
||||||
|
return `${pad(d)}.${pad(m)}.${y} (${weekday[0]})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useOutsideClose = (isOpen, setIsOpen) => {
|
||||||
|
const menuRef = React.useRef(null);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isOpen) return undefined;
|
||||||
|
const onPointerDown = (e) => {
|
||||||
|
if (menuRef.current && !menuRef.current.contains(e.target)) setIsOpen(false);
|
||||||
|
};
|
||||||
|
const onKeyDown = (e) => {
|
||||||
|
if (e.key === "Escape") setIsOpen(false);
|
||||||
|
};
|
||||||
|
document.addEventListener("pointerdown", onPointerDown);
|
||||||
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("pointerdown", onPointerDown);
|
||||||
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
}, [isOpen, setIsOpen]);
|
||||||
|
return menuRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectShell = ({ label, valueLabel, children, isOpen, setIsOpen, menuRef, wide }) => (
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<label className="mb-1 block text-xs font-medium text-[var(--color-text-muted)]">{label}</label>
|
||||||
|
<div ref={menuRef} className="relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
className={[
|
||||||
|
"flex h-[46px] w-full items-center justify-between rounded-2xl border px-4 text-left text-sm transition",
|
||||||
|
isOpen
|
||||||
|
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)] text-[var(--color-text)]"
|
||||||
|
: "border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-text)] hover:border-[var(--color-accent)]",
|
||||||
|
].join(" ")}
|
||||||
|
onClick={() => setIsOpen((c) => !c)}
|
||||||
|
>
|
||||||
|
<span className="min-w-0 flex-1 truncate">{valueLabel || "Выбрать"}</span>
|
||||||
|
<span aria-hidden="true" className="ml-3 text-[var(--color-text-muted)]">▾</span>
|
||||||
|
</button>
|
||||||
|
{isOpen ? (
|
||||||
|
<div
|
||||||
|
role="listbox"
|
||||||
|
className={[
|
||||||
|
"absolute left-0 top-full z-20 mt-2 overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-dropdown-surface)] shadow-soft",
|
||||||
|
wide ? "w-max min-w-full" : "w-full",
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const OptionRow = ({ value, selectedValue, label, onPick }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="option"
|
||||||
|
aria-selected={selectedValue === value}
|
||||||
|
className={[
|
||||||
|
"flex w-full items-center justify-between px-4 py-2.5 text-left text-sm transition",
|
||||||
|
selectedValue === value
|
||||||
|
? "bg-[var(--color-accent-soft)] text-[var(--color-text)]"
|
||||||
|
: "text-[var(--color-text)] hover:bg-[var(--color-surface-strong)]",
|
||||||
|
].join(" ")}
|
||||||
|
onClick={() => onPick(value)}
|
||||||
|
>
|
||||||
|
<span className="min-w-0 flex-1 truncate">{label}</span>
|
||||||
|
{selectedValue === value ? <span className="ml-3 text-[var(--color-accent)]">✓</span> : null}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
// ─── Date ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const buildDateOptions = (currentValue) => {
|
||||||
|
const today = new Date();
|
||||||
|
const options = [];
|
||||||
|
for (let offset = -15; offset <= 15; offset += 1) {
|
||||||
|
const d = new Date(today.getFullYear(), today.getMonth(), today.getDate() + offset);
|
||||||
|
options.push({ value: toDateInputValue(d), label: formatRuDate(toDateInputValue(d)) });
|
||||||
|
}
|
||||||
|
if (currentValue && !options.some((o) => o.value === currentValue)) {
|
||||||
|
options.unshift({ value: currentValue, label: formatRuDate(currentValue) });
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CallDateSelect = ({ value, onChange }) => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const menuRef = useOutsideClose(isOpen, setIsOpen);
|
||||||
|
const options = React.useMemo(() => buildDateOptions(value), [value]);
|
||||||
|
return (
|
||||||
|
<SelectShell label="Дата звонка" valueLabel={formatRuDate(value)} isOpen={isOpen} setIsOpen={setIsOpen} menuRef={menuRef} wide>
|
||||||
|
<div className="max-h-[280px] overflow-y-auto">
|
||||||
|
{options.map((o) => (
|
||||||
|
<OptionRow key={o.value} value={o.value} selectedValue={value} label={o.label} onPick={(v) => { onChange(v); setIsOpen(false); }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SelectShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Time ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const buildTimeOptions = (currentValue) => {
|
||||||
|
const options = [];
|
||||||
|
for (let h = 0; h < 24; h += 1) {
|
||||||
|
for (const m of ["00", "30"]) {
|
||||||
|
options.push({ value: `${pad(h)}:${m}`, label: `${pad(h)}:${m}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentValue && !options.some((o) => o.value === currentValue)) {
|
||||||
|
options.unshift({ value: currentValue, label: currentValue });
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CallTimeSelect = ({ value, onChange }) => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const menuRef = useOutsideClose(isOpen, setIsOpen);
|
||||||
|
const options = React.useMemo(() => buildTimeOptions(value), [value]);
|
||||||
|
return (
|
||||||
|
<SelectShell label="Время звонка" valueLabel={value || "Выбрать"} isOpen={isOpen} setIsOpen={setIsOpen} menuRef={menuRef}>
|
||||||
|
<div className="max-h-[280px] overflow-y-auto">
|
||||||
|
{options.map((o) => (
|
||||||
|
<OptionRow key={o.value} value={o.value} selectedValue={value} label={o.label} onPick={(v) => { onChange(v); setIsOpen(false); }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SelectShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,29 +1,54 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { supabase } from "../../supabaseClient";
|
import { supabase } from "../../supabaseClient";
|
||||||
import { Button } from "../UI/Button";
|
import { Button } from "../UI/Button";
|
||||||
|
import { CallDateSelect, CallTimeSelect, toDateInputValue, toTimeInputValue } from "./CallDateTime";
|
||||||
|
|
||||||
const CalledPanel = ({ order, onUpdate }) => {
|
const CalledPanel = ({ order, onUpdate }) => {
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [comment, setComment] = useState(order?.called_comment || "");
|
const [comment, setComment] = useState(order?.calledComment || order?.called_comment || "");
|
||||||
const [callDate, setCallDate] = useState(() => {
|
const [callDate, setCallDate] = useState("");
|
||||||
if (!order?.called_at) return "";
|
const [callTime, setCallTime] = useState("");
|
||||||
const d = new Date(order.called_at);
|
|
||||||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;
|
|
||||||
});
|
|
||||||
const [callTime, setCallTime] = useState(() => {
|
|
||||||
if (!order?.called_at) return "";
|
|
||||||
const d = new Date(order.called_at);
|
|
||||||
return `${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isCalled = !!order?.called;
|
const isCalled = !!order?.called;
|
||||||
|
|
||||||
const formatCalledAt = (ts) => {
|
const formatCalledAt = (ts) => {
|
||||||
if (!ts) return "";
|
if (!ts) return "";
|
||||||
try {
|
try {
|
||||||
return new Date(ts).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
return new Date(ts).toLocaleString("ru-RU", {
|
||||||
} catch { return ""; }
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open add form with date+time defaulted to NOW for fast entry.
|
||||||
|
const startAdd = () => {
|
||||||
|
const now = new Date();
|
||||||
|
setComment("");
|
||||||
|
setCallDate(toDateInputValue(now));
|
||||||
|
setCallTime(toTimeInputValue(now));
|
||||||
|
setEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open edit form for an existing call, pre-filled from stored values (or now).
|
||||||
|
const startEdit = () => {
|
||||||
|
setComment(order?.calledComment || order?.called_comment || "");
|
||||||
|
if (order?.calledAt || order?.called_at) {
|
||||||
|
const d = new Date(order.calledAt || order.called_at);
|
||||||
|
setCallDate(toDateInputValue(d));
|
||||||
|
setCallTime(toTimeInputValue(d));
|
||||||
|
} else {
|
||||||
|
const now = new Date();
|
||||||
|
setCallDate(toDateInputValue(now));
|
||||||
|
setCallTime(toTimeInputValue(now));
|
||||||
|
}
|
||||||
|
setEditing(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = async (newCalled) => {
|
const save = async (newCalled) => {
|
||||||
|
|
@ -40,7 +65,11 @@ const CalledPanel = ({ order, onUpdate }) => {
|
||||||
}
|
}
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from("order_groups")
|
.from("order_groups")
|
||||||
.update({ called: newCalled, called_at: calledAt, called_comment: newCalled ? comment.trim() : null })
|
.update({
|
||||||
|
called: newCalled,
|
||||||
|
called_at: calledAt,
|
||||||
|
called_comment: newCalled ? (comment.trim() || null) : null,
|
||||||
|
})
|
||||||
.eq("id", order.id);
|
.eq("id", order.id);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.warn("[CalledPanel] update error", error);
|
console.warn("[CalledPanel] update error", error);
|
||||||
|
|
@ -53,70 +82,78 @@ const CalledPanel = ({ order, onUpdate }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Form mode: date + time + comment
|
// Editing form: app-styled date/time pickers + comment + save/reset/cancel.
|
||||||
if (editing) {
|
if (editing) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4">
|
<div className="space-y-3">
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
<div>
|
<CallDateSelect value={callDate} onChange={setCallDate} />
|
||||||
<label className="block text-xs font-medium text-[var(--color-text-muted)] mb-1">Дата звонка</label>
|
<CallTimeSelect value={callTime} onChange={setCallTime} />
|
||||||
<input type="date" value={callDate} onChange={(e) => setCallDate(e.target.value)}
|
|
||||||
className="rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 text-sm !text-[var(--color-text)] focus:border-[var(--color-accent)] focus:outline-none" />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-[var(--color-text-muted)] mb-1">Время звонка</label>
|
<label className="mb-1 block text-xs font-medium text-[var(--color-text-muted)]">Комментарий</label>
|
||||||
<input type="time" value={callTime} onChange={(e) => setCallTime(e.target.value)}
|
<textarea
|
||||||
className="rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 text-sm !text-[var(--color-text)] focus:border-[var(--color-accent)] focus:outline-none" />
|
value={comment}
|
||||||
</div>
|
onChange={(e) => setComment(e.target.value)}
|
||||||
</div>
|
placeholder="Результат звонка..."
|
||||||
<div>
|
rows={2}
|
||||||
<label className="block text-xs font-medium text-[var(--color-text-muted)] mb-1">Комментарий</label>
|
className="w-full rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-3 text-sm text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-accent)] focus:outline-none resize-none"
|
||||||
<textarea value={comment} onChange={(e) => setComment(e.target.value)} placeholder="Результат звонка..." rows={2}
|
/>
|
||||||
className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 text-sm !text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:border-[var(--color-accent)] focus:outline-none resize-none" />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Button variant="primary" size="sm" onClick={() => save(true)} disabled={busy}>
|
<Button variant="primary" size="sm" onClick={() => save(true)} disabled={busy}>
|
||||||
{busy ? "Сохраняем..." : "Сохранить"}
|
{busy ? "Сохраняем..." : "Сохранить"}
|
||||||
</Button>
|
</Button>
|
||||||
{isCalled && (
|
{isCalled && (
|
||||||
<Button variant="ghost" size="sm" onClick={() => save(false)} disabled={busy}>Сбросить</Button>
|
<Button variant="ghost" size="sm" onClick={() => save(false)} disabled={busy}>
|
||||||
|
Сбросить
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button variant="secondary" size="sm" onClick={() => setEditing(false)} disabled={busy}>Отмена</Button>
|
<Button variant="secondary" size="sm" onClick={() => setEditing(false)} disabled={busy}>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default mode: buttons like status delivery
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{isCalled ? (
|
{isCalled ? (
|
||||||
<>
|
<>
|
||||||
<Button variant="primary" size="sm" onClick={() => setEditing(true)} disabled={busy}>
|
<Button variant="primary" size="sm" disabled>
|
||||||
📞 Звонили
|
📞 Звонили
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" size="sm" onClick={() => save(false)} disabled={busy}>
|
<Button variant="secondary" size="sm" onClick={() => save(false)} disabled={busy}>
|
||||||
Не звонили
|
Не звонили
|
||||||
</Button>
|
</Button>
|
||||||
{order?.called_at && (
|
<Button variant="ghost" size="sm" onClick={startEdit} disabled={busy} title="Изменить дату или комментарий">
|
||||||
<span className="text-xs text-[var(--color-text-muted)]">{formatCalledAt(order.called_at)}</span>
|
✎ Изменить
|
||||||
)}
|
</Button>
|
||||||
{order?.called_comment && (
|
{order?.calledAt || order?.called_at ? (
|
||||||
<span className="text-xs text-[var(--color-text-muted)] italic">«{order.called_comment}»</span>
|
<span className="text-xs text-[var(--color-text-muted)]">{formatCalledAt(order.calledAt || order.called_at)}</span>
|
||||||
)}
|
) : null}
|
||||||
|
{order?.calledComment || order?.called_comment ? (
|
||||||
|
<span className="text-xs text-[var(--color-text-muted)] italic">«{order.calledComment || order.called_comment}»</span>
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button variant="ghost" size="sm" onClick={() => setEditing(true)} disabled={busy}>
|
<Button variant="primary" size="sm" onClick={() => save(true)} disabled={busy}>
|
||||||
📞 Звонили
|
{busy ? "Сохраняем..." : "📞 Звонили"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" size="sm" disabled>
|
<Button variant="ghost" size="sm" onClick={startAdd} disabled={busy} title="Записать дату и время звонка">
|
||||||
Не звонили
|
✎ Дата/комментарий
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{!isCalled && (
|
||||||
|
<p className="text-xs text-[var(--color-text-muted)]">
|
||||||
|
Нажмите «Звонили», чтобы отметить звонок прямо сейчас.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -663,6 +663,7 @@ export const OrderDetailPanel = ({
|
||||||
onAssignDriver,
|
onAssignDriver,
|
||||||
onChangeDeliveryStatus,
|
onChangeDeliveryStatus,
|
||||||
onSaveShipmentData,
|
onSaveShipmentData,
|
||||||
|
onRefreshOrder,
|
||||||
userRole = "driver",
|
userRole = "driver",
|
||||||
}) => {
|
}) => {
|
||||||
const [problemReason, setProblemReason] = React.useState(null);
|
const [problemReason, setProblemReason] = React.useState(null);
|
||||||
|
|
@ -1090,7 +1091,7 @@ export const OrderDetailPanel = ({
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
canManageDelivery={canManageDelivery}
|
canManageDelivery={canManageDelivery}
|
||||||
isSavingStatusChange={isSavingStatusChange}
|
isSavingStatusChange={isSavingStatusChange}
|
||||||
onRefreshOrder={() => {}}
|
onRefreshOrder={onRefreshOrder}
|
||||||
onConfirmStatus={(action) => {
|
onConfirmStatus={(action) => {
|
||||||
if (action.type === "hint") {
|
if (action.type === "hint") {
|
||||||
setFormMessage(action.hint);
|
setFormMessage(action.hint);
|
||||||
|
|
@ -1666,6 +1667,7 @@ export const OrderDetailPanel = ({
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
canManageDelivery={canManageDelivery}
|
canManageDelivery={canManageDelivery}
|
||||||
isSavingStatusChange={isSavingStatusChange}
|
isSavingStatusChange={isSavingStatusChange}
|
||||||
|
onRefreshOrder={onRefreshOrder}
|
||||||
onConfirmStatus={(action) => {
|
onConfirmStatus={(action) => {
|
||||||
if (action.type === "hint") {
|
if (action.type === "hint") {
|
||||||
setFormMessage(action.hint);
|
setFormMessage(action.hint);
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ export const GroupDetailPage = () => {
|
||||||
onAssignDriver={assignDriver}
|
onAssignDriver={assignDriver}
|
||||||
onChangeDeliveryStatus={changeDeliveryStatus}
|
onChangeDeliveryStatus={changeDeliveryStatus}
|
||||||
onSaveShipmentData={saveShipmentData}
|
onSaveShipmentData={saveShipmentData}
|
||||||
|
onRefreshOrder={() => refetch(false)}
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
|
||||||
|
|
@ -245,6 +245,11 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
||||||
managerTel: normalizeText(row.manager_tel) || null,
|
managerTel: normalizeText(row.manager_tel) || null,
|
||||||
payedShip: row.payed_ship || null,
|
payedShip: row.payed_ship || null,
|
||||||
isPayedShip: row.is_payed_ship || false,
|
isPayedShip: row.is_payed_ship || false,
|
||||||
|
called: !!row.called,
|
||||||
|
calledAt: row.called_at || null,
|
||||||
|
called_at: row.called_at || null,
|
||||||
|
calledComment: row.called_comment || null,
|
||||||
|
called_comment: row.called_comment || null,
|
||||||
invitationOpenedAt: row.delivery_invitation?.opened_at || null,
|
invitationOpenedAt: row.delivery_invitation?.opened_at || null,
|
||||||
invitationAccessCount: row.delivery_invitation?.access_count || 0,
|
invitationAccessCount: row.delivery_invitation?.access_count || 0,
|
||||||
invitationLastAccessedAt: row.delivery_invitation?.last_accessed_at || null,
|
invitationLastAccessedAt: row.delivery_invitation?.last_accessed_at || null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue