pc setup v2: one-screen layout with a time slider, photo grid and lightbox
All checks were successful
Deploy production / deploy (push) Successful in 1m7s
All checks were successful
Deploy production / deploy (push) Successful in 1m7s
- hardware: slots, sections, usage, install/removal dates, images and a hidden flag on components and devices; setup_photos table; setup_overview view gains the new columns - data: hide the 2021 friends-PC build and the wrong 140mm / unknown fans (kept, not deleted); 7x SL-INF + 3 Kraken stock fans; mice, audio, desk extras; MacBook Pro 16 M2 Pro 1 TB; dates for every swap so the slider can rebuild the setup on any day - self-hosted product images in static/hardware (webp + thumbs) - prices and order details are owner-only (in a popover)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { sql } from 'drizzle-orm';
|
||||
import {
|
||||
type AnyPgColumn,
|
||||
bigint,
|
||||
boolean,
|
||||
check,
|
||||
@@ -49,9 +50,30 @@ export const HARDWARE_CATEGORIES = [
|
||||
'audio',
|
||||
'controller',
|
||||
'accessory',
|
||||
'microphone',
|
||||
'mousepad',
|
||||
'webcam',
|
||||
'chair',
|
||||
'other'
|
||||
] as const;
|
||||
export const HARDWARE_STATUSES = ['in_use', 'planned', 'spare', 'retired', 'sold'] as const;
|
||||
/** Page sections on /trackers/pc. */
|
||||
export const HARDWARE_SECTIONS = ['pc', 'desk', 'audio', 'sim'] as const;
|
||||
/**
|
||||
* How a part shows on the time slider: current, stored (owned, not set up), previous (gone,
|
||||
* dated by removed_at), work-issued, placeholder (model TBD), legacy (owned long before the
|
||||
* mail history, still owned) and legacy-previous (long ago and gone, date unknown).
|
||||
*/
|
||||
export const HARDWARE_USAGES = [
|
||||
'current',
|
||||
'stored',
|
||||
'previous',
|
||||
'work-issued',
|
||||
'placeholder',
|
||||
'planned',
|
||||
'legacy',
|
||||
'legacy-previous'
|
||||
] as const;
|
||||
|
||||
const list = (values: readonly string[]) => sql.raw(values.map((v) => `'${v}'`).join(', '));
|
||||
|
||||
@@ -85,6 +107,15 @@ export const hardwareDevices = hardwareSchema.table(
|
||||
imageUrl: text('image_url'),
|
||||
sortOrder: integer('sort_order').notNull().default(100),
|
||||
notes: text('notes'),
|
||||
/** Time slider: when it joined / left the setup (null = unknown / still here). */
|
||||
installedAt: date('installed_at'),
|
||||
removedAt: date('removed_at'),
|
||||
/** Self-hosted product photos (/hardware/...webp), first is the thumbnail. */
|
||||
imageUrls: text('image_urls').array().notNull().default([]),
|
||||
/** Where each image was downloaded from (attribution), same order as image_urls. */
|
||||
imageSources: text('image_sources').array().notNull().default([]),
|
||||
/** Kept for history but not shown (e.g. a PC built for someone else). */
|
||||
hidden: boolean('hidden').notNull().default(false),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamptz('updated_at')
|
||||
.notNull()
|
||||
@@ -133,6 +164,23 @@ export const hardwareComponents = hardwareSchema.table(
|
||||
notes: text('notes'),
|
||||
/** user_list | gmail | user_statement | manual | plan. */
|
||||
source: text('source'),
|
||||
/** Stable slug for merges/seeds, e.g. cpu-12900kf. */
|
||||
slug: text('slug').unique(),
|
||||
/** Page section (pc/desk/audio/sim) and slot within it (cpu, fans-case, mouse-gaming…). */
|
||||
section: text('section'),
|
||||
slot: text('slot'),
|
||||
/** Time slider: in the setup from installed_at until removed_at (null = unknown / still). */
|
||||
installedAt: date('installed_at'),
|
||||
removedAt: date('removed_at'),
|
||||
replacesId: bigint('replaces_id', { mode: 'number' }).references(
|
||||
(): AnyPgColumn => hardwareComponents.id,
|
||||
{ onDelete: 'set null' }
|
||||
),
|
||||
usage: text('usage'),
|
||||
imageUrls: text('image_urls').array().notNull().default([]),
|
||||
imageSources: text('image_sources').array().notNull().default([]),
|
||||
/** Kept for history but not shown (wrong placeholders, a friend's build). */
|
||||
hidden: boolean('hidden').notNull().default(false),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamptz('updated_at')
|
||||
.notNull()
|
||||
@@ -143,6 +191,15 @@ export const hardwareComponents = hardwareSchema.table(
|
||||
check('components_category_check', sql`category in (${list(HARDWARE_CATEGORIES)})`),
|
||||
check('components_status_check', sql`status in (${list(HARDWARE_STATUSES)})`),
|
||||
check('components_quantity_check', sql`quantity > 0`),
|
||||
check(
|
||||
'components_section_check',
|
||||
sql`section is null or section in (${list(HARDWARE_SECTIONS)})`
|
||||
),
|
||||
check('components_usage_check', sql`usage is null or usage in (${list(HARDWARE_USAGES)})`),
|
||||
check(
|
||||
'components_dates_check',
|
||||
sql`removed_at is null or installed_at is null or removed_at >= installed_at`
|
||||
),
|
||||
check('components_unit_price_cents_check', sql`unit_price_cents >= 0`),
|
||||
check('components_total_price_cents_check', sql`total_price_cents >= 0`),
|
||||
check(
|
||||
@@ -152,10 +209,21 @@ export const hardwareComponents = hardwareSchema.table(
|
||||
unique('components_dedupe_uq').on(t.deviceId, t.model, t.orderNumber).nullsNotDistinct(),
|
||||
index('components_device_idx').on(t.deviceId),
|
||||
index('components_category_idx').on(t.category),
|
||||
index('components_specs_gin').using('gin', t.specs)
|
||||
index('components_specs_gin').using('gin', t.specs),
|
||||
index('components_timeline_idx').on(t.installedAt, t.removedAt)
|
||||
]
|
||||
);
|
||||
|
||||
/** Stan's own photos of the whole setup (hero carousel); taken_at lets the slider pick one. */
|
||||
export const hardwareSetupPhotos = hardwareSchema.table('setup_photos', {
|
||||
id: bigint('id', { mode: 'number' }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
url: text('url').notNull(),
|
||||
caption: text('caption'),
|
||||
takenAt: date('taken_at'),
|
||||
sortOrder: integer('sort_order').notNull().default(100),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow()
|
||||
});
|
||||
|
||||
/**
|
||||
* One row per game: the physical sensitivity Stan wants (cm per 360° turn) and, where the
|
||||
* game's yaw is known (degrees per mouse count at sens 1, e.g. CS2 0.022), the in-game value
|
||||
|
||||
Reference in New Issue
Block a user