feat: Sprint 0 — project scaffold, DB schema, Zustand stores, base UI components

This commit is contained in:
root
2026-02-18 06:21:22 +08:00
commit 8f66a4d4ed
22 changed files with 497 additions and 0 deletions

114
lib/database.ts Normal file
View File

@@ -0,0 +1,114 @@
import * as SQLite from 'expo-sqlite';
let db: SQLite.SQLiteDatabase | null = null;
export async function getDatabase(): Promise<SQLite.SQLiteDatabase> {
if (!db) {
db = await SQLite.openDatabaseAsync('territorylog.db');
await initSchema(db);
}
return db;
}
async function initSchema(database: SQLite.SQLiteDatabase): Promise<void> {
await database.execAsync(`
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
display_name TEXT NOT NULL,
share_id TEXT UNIQUE NOT NULL,
is_self INTEGER DEFAULT 1,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS territories (
id TEXT PRIMARY KEY,
territory_code TEXT UNIQUE NOT NULL,
municipality TEXT,
barangay TEXT,
area TEXT,
block TEXT,
assigned_to TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS contacts (
id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
full_name TEXT NOT NULL,
address TEXT,
household_count INTEGER DEFAULT 1,
gender TEXT,
category TEXT NOT NULL,
status TEXT DEFAULT 'Active',
tags TEXT DEFAULT '[]',
notes TEXT,
territory_code TEXT REFERENCES territories(territory_code),
latitude REAL,
longitude REAL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
deleted_at INTEGER
);
CREATE TABLE IF NOT EXISTS visits (
id TEXT PRIMARY KEY,
contact_id TEXT NOT NULL REFERENCES contacts(id),
visited_by_name TEXT NOT NULL,
visited_by_id TEXT NOT NULL,
visit_date INTEGER NOT NULL,
topic TEXT,
response TEXT,
remarks TEXT,
next_visit_date INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS topics (
id TEXT PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
is_default INTEGER DEFAULT 0,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS sync_log (
id TEXT PRIMARY KEY,
partner_id TEXT NOT NULL,
partner_name TEXT,
synced_at INTEGER NOT NULL,
sent_count INTEGER DEFAULT 0,
received_count INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(full_name);
CREATE INDEX IF NOT EXISTS idx_contacts_status ON contacts(status);
CREATE INDEX IF NOT EXISTS idx_contacts_territory ON contacts(territory_code);
CREATE INDEX IF NOT EXISTS idx_visits_contact ON visits(contact_id);
CREATE INDEX IF NOT EXISTS idx_visits_date ON visits(visit_date);
`);
// Seed default topics
const now = Math.floor(Date.now() / 1000);
const defaultTopics = [
{ id: 't1', name: 'The Kingdom of God' },
{ id: 't2', name: 'Paradise Earth' },
{ id: 't3', name: 'Life After Death' },
{ id: 't4', name: "The Bible's Reliability" },
{ id: 't5', name: "God's Name (Jehovah)" },
{ id: 't6', name: 'Why Bad Things Happen' },
{ id: 't7', name: 'Jesus Christ' },
{ id: 't8', name: 'The Resurrection' },
{ id: 't9', name: 'Family Happiness' },
];
for (const topic of defaultTopics) {
await database.runAsync(
'INSERT OR IGNORE INTO topics (id, name, is_default, created_at) VALUES (?, ?, 1, ?)',
[topic.id, topic.name, now]
);
}
}