house tracker: contact status per listing (whose turn, last message, viewing) and labelled filters
Some checks failed
Deploy production / deploy (push) Has been cancelled
Some checks failed
Deploy production / deploy (push) Has been cancelled
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
// bun run house add --url <link> --city Gouda --price 1422 --area 84 --bedrooms 2
|
||||
// bun run house add --json listing.json (or `--json -` for stdin; may be an array)
|
||||
// bun run house update <id|url> --status viewing --notes "Sat 14:00"
|
||||
// bun run house list [--all] [--city gouda] [--offer buy] [--json]
|
||||
// bun run house update <id|url> --contact waiting_on_them --contact-at 2026-10-06 --contact-by me \
|
||||
// --contact-note "Applied via form" --contact-ref "gmail thread …"
|
||||
// bun run house list [--all] [--city gouda] [--offer buy] [--contact any|<s>] [--json]
|
||||
// bun run house remove <id|url> [--hard] (soft remove = marked gone)
|
||||
// bun run house restore <id|url>
|
||||
//
|
||||
@@ -37,7 +39,8 @@ const usage = `usage: bun run house <add|update|list|remove|restore> [options]
|
||||
|
||||
add [--json <file|->] [field flags] insert, or update when the same link/address exists
|
||||
update <id|url> [field flags] change only the given fields (use "" to clear one)
|
||||
list [--all] [--city <slug>] [--offer rent|buy] [--status <s>] [--limit n] [--json]
|
||||
list [--all] [--city <slug>] [--offer rent|buy] [--status <s>] [--contact any|<s>]
|
||||
[--limit n] [--json]
|
||||
remove <id|url> [--hard] mark gone (or delete with --hard)
|
||||
restore <id|url> undo a soft remove
|
||||
|
||||
@@ -47,6 +50,9 @@ field flags: --url --offer rent|buy --type apartment|house|room|studio|other --t
|
||||
--contract-type --agent --image-url --description --notes --status
|
||||
interested|contacted|viewing|applied|rejected --lat --lng --offered-at
|
||||
--first-seen (when it was shared) --source --source-id
|
||||
contact: --contact waiting_on_me|not_contacted|waiting_on_them|viewing|closed
|
||||
--contact-at <date> (last message) --contact-by me|them --contact-note (one line)
|
||||
--contact-ref (mail thread / chat) --viewing-at <date time>
|
||||
other: --dry-run (print, don't write)`;
|
||||
|
||||
const fieldFlags = [
|
||||
@@ -80,7 +86,13 @@ const fieldFlags = [
|
||||
'first-seen',
|
||||
'source',
|
||||
'source-id',
|
||||
'external-id'
|
||||
'external-id',
|
||||
'contact',
|
||||
'contact-at',
|
||||
'contact-by',
|
||||
'contact-note',
|
||||
'contact-ref',
|
||||
'viewing-at'
|
||||
] as const;
|
||||
|
||||
const { values: flags, positionals } = parseArgs({
|
||||
@@ -145,7 +157,8 @@ const sql = postgres(dbUrl, { max: 1, onnotice: () => {} });
|
||||
type Tx = postgres.TransactionSql;
|
||||
|
||||
const summary = sql`id, source, source_id, offer, property_type, title, city, city_slug,
|
||||
price_eur, area_m2, rooms, bedrooms, status, removed_at, url`;
|
||||
price_eur, area_m2, rooms, bedrooms, status, removed_at, url, contact_status, contact_at,
|
||||
contact_by, contact_note, contact_ref, viewing_at`;
|
||||
|
||||
async function findId(ref: string | undefined): Promise<number> {
|
||||
if (!ref) throw new Error('give a listing id or url');
|
||||
@@ -336,12 +349,20 @@ async function list() {
|
||||
const city = flag('city');
|
||||
const offer = flag('offer');
|
||||
const status = flag('status');
|
||||
const contact = flag('contact');
|
||||
const rows = await sql`select ${summary}, first_seen_at, notes from house.listings
|
||||
where true
|
||||
${flag('all') ? sql`` : sql`and removed_at is null`}
|
||||
${typeof city === 'string' ? sql`and city_slug = ${city}` : sql``}
|
||||
${typeof offer === 'string' ? sql`and offer = ${offer}` : sql``}
|
||||
${typeof status === 'string' ? sql`and status = ${status}` : sql``}
|
||||
${
|
||||
contact === 'any'
|
||||
? sql`and contact_status is not null`
|
||||
: typeof contact === 'string'
|
||||
? sql`and contact_status = ${contact}`
|
||||
: sql``
|
||||
}
|
||||
order by first_seen_at desc limit ${limit}`;
|
||||
if (flag('json')) console.log(JSON.stringify(rows, null, 2));
|
||||
else print(rows);
|
||||
@@ -378,6 +399,17 @@ function print(rows: readonly Record<string, unknown>[]) {
|
||||
console.log(
|
||||
`${r.action ? `${r.action} ` : ''}#${r.id} [${r.source}] ${r.title ?? '(no title)'}, ${r.city ?? '?'} · ${bits.join(' · ')}\n ${r.url}`
|
||||
);
|
||||
if (r.contact_status) {
|
||||
const day = (d: unknown) => (d instanceof Date ? d.toISOString().slice(0, 16) : d);
|
||||
const contact = [
|
||||
r.contact_status,
|
||||
r.viewing_at && `viewing ${day(r.viewing_at)}`,
|
||||
r.contact_at && `last ${day(r.contact_at)}${r.contact_by ? ` by ${r.contact_by}` : ''}`,
|
||||
r.contact_note,
|
||||
r.contact_ref && `ref: ${r.contact_ref}`
|
||||
].filter(Boolean);
|
||||
console.log(` contact: ${contact.join(' · ')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user