feat: fix issue #381 by scoping the import and sheet-sync entry points on a segment's member list to that segment, so a CSV uploaded from inside a segment pins its rows in instead of creating contacts that are nowhere in it, and round the page out with an empty state offering Add contacts, Import file and New contact plus a pinned-contacts panel that says when it is showing only the newest slice of a large import

This commit is contained in:
Matthew Meszaros
2026-09-08 04:59:25 -07:00
parent 5f665a3208
commit 16349852cc
2 changed files with 38 additions and 4 deletions
@@ -127,7 +127,7 @@ function SegmentDetail() {
{(s.included_count > 0 || s.excluded_count > 0) && <OverridesPanel segment={s} />}
<ContactsTable key={s.id} segment={{ id: s.id, name: s.name }} />
<ContactsTable key={s.id} segment={{ id: s.id, name: s.name, color: s.color }} />
<SegmentEditor open={editorOpen} onClose={() => setEditorOpen(false)} segment={s} />
<AddSegmentToCampaignDialog open={campaignOpen} onClose={() => setCampaignOpen(false)} segment={s} />
@@ -190,6 +190,10 @@ function OverridesPanel({ segment }: { segment: Segment }) {
}
const list = overrides.data ?? [];
// The API caps one listing, so a segment a big import pinned into shows a
// slice of its overrides. Say so rather than implying this is all of them.
const pinned = segment.included_count + segment.excluded_count;
const truncated = pinned > list.length;
return (
<div className="border-b border-slate-200 bg-slate-50/40">
<button
@@ -232,6 +236,12 @@ function OverridesPanel({ segment }: { segment: Segment }) {
</li>
);
})}
{truncated && (
<li className="px-5 h-9 flex items-center text-[11.5px] text-slate-400">
Showing the newest {list.length.toLocaleString()} of {pinned.toLocaleString()}. Search the member
list below to reach the rest.
</li>
)}
</ul>
)}
</div>
@@ -117,7 +117,7 @@ export default function ContactsTable({
}: {
current_campaign?: MiniCampaign;
// Scope the list to one segment's members (the segment detail page).
segment?: { id: string; name: string };
segment?: { id: string; name: string; color?: string };
}) {
const confirm = useConfirm();
const segmentMembers = useSetSegmentMembers();
@@ -487,7 +487,7 @@ export default function ContactsTable({
? "Pick people from your contacts, import a file, or add one by hand. The linked segments could not be loaded."
: "Pick people from your contacts, link a segment, import a file, or add one by hand."
: segment
? "Nobody matches its conditions yet. Adjust them or pin contacts in."
? "Nothing matches it yet. Pin people in from your contacts, import a file, or add one by hand."
: "Add or upload contacts to get started."
}
emptyCta={
@@ -532,6 +532,29 @@ export default function ContactsTable({
Import file
</TopbarAction>
</div>
) : segment ? (
<div className="flex flex-wrap items-center justify-center gap-1.5">
<TopbarAction
icon={<UsersIcon className="w-3 h-3" />}
onClick={() => setFromContactsOpen(true)}
>
Add contacts
</TopbarAction>
<TopbarAction
variant="ghost"
icon={<UploadIcon className="w-3 h-3" />}
onClick={() => setImportOpen(true)}
>
Import file
</TopbarAction>
<TopbarAction
variant="ghost"
icon={<UserPlusIcon className="w-3 h-3" />}
onClick={() => setNewOpen(true)}
>
New contact
</TopbarAction>
</div>
) : (
<TopbarAction
icon={<UserPlusIcon className="w-3 h-3" />}
@@ -931,8 +954,9 @@ export default function ContactsTable({
<ImportWizard
open={importOpen}
onClose={() => setImportOpen(false)}
lockedSegment={segment}
/>
<SyncSourcesPanel open={syncOpen} onClose={() => setSyncOpen(false)} />
<SyncSourcesPanel open={syncOpen} onClose={() => setSyncOpen(false)} segment={segment} />
</Page>
);
}