🗄️ Supabase 설정 가이드

인 다이렉트 카보험 – 공용 DB 초기 설정 (약 5분 소요)

⚠️
522 오류 – Supabase 프로젝트가 일시 정지 상태입니다
Supabase 무료 플랜은 1주일 이상 미접속 시 프로젝트가 자동 정지됩니다.
아래 버튼을 클릭해 프로젝트를 다시 활성화하면 즉시 해결됩니다.
🔄 Supabase 대시보드에서 재활성화
📋 재활성화 방법 (30초 소요)
1 위 버튼 클릭 → Supabase 대시보드 열기 (로그인 필요)
2 화면 중앙에 "This project is paused" 또는 "프로젝트가 일시 정지됨" 배너가 보임
3 "Restore project" 버튼 클릭 → 1~2분 대기 (초록색 점이 켜지면 완료)
4 이 페이지로 돌아와서 "🔗 재활성화 후 연결 재시도" 클릭
💡 Supabase 무료 플랜 자동 정지 정책: 7일 이상 API 요청이 없으면 자동 정지됩니다.
→ 정지 방지: 주기적으로 로그인하거나, Pro 플랜($25/월)으로 업그레이드하면 자동 정지가 없습니다.
→ 2달 내 광고·고객 유입 계획이 있으시다면 Pro 플랜 전환을 권장합니다.
① 대시보드
접속
② SQL Editor
열기
③ SQL
붙여넣기
④ Run
실행
⑤ 연결
테스트
🎉
Supabase 설정 완료!

DB 테이블이 생성됐습니다. 이제 직원 전산에서 로그인하여 사용하세요.

🔐 직원 로그인 📊 대시보드
🔑 연결 정보 확인
Project URL https://dzxqzmklagmosfdgycpj.supabase.co
Anon Key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImR6eHF6bWtsYWdtb3NmZGd5Y3BqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzczNDY2ODYsImV4cCI6MjA5MjkyMjY4Nn0.RP5slzjULBLShZBbBc9rs6V_VAtjHYANybDGzXst9TA
Region 🇯🇵 ap-northeast-1 (도쿄)
프로젝트명 dzxqzmklagmosfdgycpj
ℹ️ Anon Key는 브라우저에 공개되는 읽기 키입니다. 민감 데이터는 Row Level Security(RLS)로 보호됩니다.
🖥️ STEP 1 — Supabase 대시보드 접속
1

아래 버튼을 클릭해 대시보드를 여세요

🚀 Supabase 대시보드 열기
💡 로그인이 필요합니다. Supabase 계정이 없으면 supabase.com 에서 무료 가입하세요.
supabase.com/dashboard/project/dzxqzmklagmosfdgycpj
S
🏠
📋
⌨️
🔒
좌측 메뉴에서 찾아야 할 항목:
⌨️ SQL Editor ← 이것을 클릭!
📝 STEP 2 — SQL Editor 열기
1

좌측 메뉴에서 SQL Editor 클릭

대시보드 왼쪽 사이드바에서 ⌨️ SQL Editor 를 찾아 클릭하세요.

2

New Query 버튼 클릭

SQL Editor가 열리면 우측 상단 또는 좌측 패널에서 + New query 버튼을 클릭하세요.

supabase.com/dashboard/project/.../sql/new
SQL EDITOR
+ New query
또는 좌측 패널에서도 클릭 가능
-- 여기에 SQL을 붙여넣으면 됩니다
|
▶ Run
📋 STEP 3 — SQL 전체 복사 & 붙여넣기
1

아래 버튼으로 SQL 전체 복사

✅ 복사 후 SQL Editor 화면에 Ctrl + V (Mac: Cmd+V) 로 붙여넣으세요.
2

SQL 내용 (참고용)

-- ================================================================
-- 인 다이렉트 카보험  Supabase 완전 설치 SQL  v3.0
-- ================================================================
-- ✅ 이미 실행한 적 있어도 안전하게 재실행 가능 (멱등성 보장)
-- ✅ 기존 데이터는 절대 삭제되지 않습니다
-- ⚠️ 직원 비밀번호 평문 저장 — 내부 전산 전용
-- ================================================================

-- [STEP 0] 확장 모듈
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "unaccent";

-- [STEP 1] 테이블 생성 ──────────────────────────────────────────

-- ① 직원
CREATE TABLE IF NOT EXISTS staff (
  id          UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  staff_id    TEXT        NOT NULL UNIQUE,
  password    TEXT        NOT NULL,   -- ⚠️ 평문 저장 — 내부 전산 전용
  name        TEXT        NOT NULL,
  role        TEXT        NOT NULL DEFAULT 'staff'
                          CHECK (role IN ('admin','secretary','staff')),
  dept        TEXT, title TEXT, phone TEXT, email TEXT,
  permissions JSONB       NOT NULL DEFAULT '[]'::JSONB,
  active      BOOLEAN     NOT NULL DEFAULT true,
  last_login  TIMESTAMPTZ,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted     BOOLEAN     NOT NULL DEFAULT false
);

-- ② 고객
CREATE TABLE IF NOT EXISTS customers (
  id               UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  name             TEXT        NOT NULL,
  phone            TEXT, email TEXT,
  gender           TEXT        CHECK (gender IN ('male','female') OR gender IS NULL OR gender = ''),
  birth_year       TEXT, resident_num TEXT,
  address          TEXT, bank_account TEXT, card_number TEXT,
  car_number       TEXT, car_model TEXT,
  insurer          TEXT, insurance_company TEXT,
  start_date       DATE, expiry_date DATE,
  premium          NUMERIC(15,0),
  contract_type    TEXT, referrer TEXT, assigned_staff TEXT,
  status           TEXT        NOT NULL DEFAULT 'active'
                               CHECK (status IN ('active','vip','warning','inactive','new_joining','new_done')),
  notice           TEXT,
  photos           JSONB       NOT NULL DEFAULT '[]'::JSONB,
  profile_photo    TEXT,
  join_date        DATE        DEFAULT CURRENT_DATE,
  source           TEXT        NOT NULL DEFAULT 'staff'
                               CHECK (source IN ('staff','web','ad_kakao','ad_naver','ad_insta','ad_google','dm','partner','referral')),
  created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted          BOOLEAN     NOT NULL DEFAULT false
);

-- ③ 계약
CREATE TABLE IF NOT EXISTS contracts (
  id               UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id      UUID        REFERENCES customers(id) ON DELETE SET NULL ON UPDATE CASCADE,
  car_number       TEXT, car_model TEXT, insurer TEXT,
  insured          TEXT, insured_phone TEXT, insured_rrn TEXT,
  contractor       TEXT, contractor_phone TEXT,
  contractor_rrn   TEXT, contractor_addr TEXT,
  start_date       DATE, expiry_date DATE,
  premium          NUMERIC(15,0),
  renewal_status   TEXT        NOT NULL DEFAULT 'pending'
                               CHECK (renewal_status IN ('pending','contacted','quoted','confirmed','completed','canceled')),
  renewal_note     TEXT, contract_type TEXT,
  referrer         TEXT, assigned_to TEXT, staff_name TEXT, memo TEXT,
  policy_number    TEXT, imported_at TIMESTAMPTZ,
  email            TEXT, address TEXT, bank_info TEXT,
  card_number      TEXT, payment_info TEXT,
  status           TEXT        NOT NULL DEFAULT 'active'
                               CHECK (status IN ('active','inactive')),
  created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted          BOOLEAN     NOT NULL DEFAULT false
);

-- ④ 파트너
CREATE TABLE IF NOT EXISTS partners (
  id             UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  partner_id     TEXT        NOT NULL UNIQUE,
  password       TEXT        NOT NULL, name TEXT NOT NULL,
  type           TEXT        NOT NULL DEFAULT 'dealer'
                             CHECK (type IN ('dealer','agent')),
  phone          TEXT, contact TEXT, dealer_name TEXT,
  email          TEXT, biz_number TEXT, address TEXT,
  status         TEXT        NOT NULL DEFAULT 'pending'
                             CHECK (status IN ('pending','approved','rejected','suspended')),
  assigned_staff TEXT, notice TEXT, memo TEXT, reject_reason TEXT,
  contract_count INTEGER     NOT NULL DEFAULT 0,
  photos         JSONB       NOT NULL DEFAULT '[]'::JSONB,
  profile_photo  TEXT,
  join_date      DATE        DEFAULT CURRENT_DATE,
  created_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted        BOOLEAN     NOT NULL DEFAULT false
);

-- ⑤ 신규 유입 (leads)
CREATE TABLE IF NOT EXISTS leads (
  id            UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  name          TEXT        NOT NULL, phone TEXT NOT NULL,
  car_number    TEXT, car_model TEXT,
  insurer       TEXT, expiry_date DATE, message TEXT,
  source        TEXT        NOT NULL DEFAULT 'web'
                            CHECK (source IN ('web','ad_kakao','ad_naver','ad_insta','ad_google','dm','partner','referral')),
  ad_code       TEXT, utm_source TEXT, utm_medium TEXT, utm_campaign TEXT,
  type          TEXT,
  status        TEXT        NOT NULL DEFAULT 'new'
                            CHECK (status IN ('new','contacted','consulting','converted','cancel','lost')),
  internal_memo TEXT, done_at TIMESTAMPTZ,
  assigned_to   TEXT, memo TEXT,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted       BOOLEAN     NOT NULL DEFAULT false
);

-- ⑥ 공지사항
CREATE TABLE IF NOT EXISTS notices (
  id         UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  title      TEXT        NOT NULL, content TEXT,
  priority   TEXT        NOT NULL DEFAULT 'normal'
             CHECK (priority IN ('normal','important','urgent')),
  author     TEXT, author_id TEXT,
  target     TEXT        NOT NULL DEFAULT 'all'
             CHECK (target IN ('all','staff','partner','customer')),
  popup      BOOLEAN     NOT NULL DEFAULT false,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted    BOOLEAN     NOT NULL DEFAULT false
);

-- ⑦ 이력 로그 (불변 append-only)
CREATE TABLE IF NOT EXISTS history_logs (
  id          UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  target_id   TEXT        NOT NULL,
  target_type TEXT        NOT NULL
              CHECK (target_type IN ('customer','contract','partner','lead','staff','notice')),
  staff_id    TEXT, staff_name TEXT, action TEXT NOT NULL,
  detail      JSONB       NOT NULL DEFAULT '{}'::JSONB,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- [STEP 2] 누락 컬럼 보완 (구 DB 호환) ─────────────────────────
ALTER TABLE customers ADD COLUMN IF NOT EXISTS insurance_company TEXT;
ALTER TABLE customers ADD COLUMN IF NOT EXISTS card_number       TEXT;
ALTER TABLE customers ADD COLUMN IF NOT EXISTS bank_account      TEXT;
ALTER TABLE customers ADD COLUMN IF NOT EXISTS address           TEXT;
ALTER TABLE customers ADD COLUMN IF NOT EXISTS email             TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS insured_rrn       TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS contractor_rrn    TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS contractor_addr   TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS renewal_note      TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS policy_number     TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS imported_at       TIMESTAMPTZ;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS email             TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS address           TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS bank_info         TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS card_number       TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS payment_info      TEXT;
ALTER TABLE contracts ADD COLUMN IF NOT EXISTS staff_name        TEXT;
ALTER TABLE partners  ADD COLUMN IF NOT EXISTS contact           TEXT;
ALTER TABLE partners  ADD COLUMN IF NOT EXISTS dealer_name       TEXT;
ALTER TABLE partners  ADD COLUMN IF NOT EXISTS contract_count    INTEGER NOT NULL DEFAULT 0;
ALTER TABLE partners  ADD COLUMN IF NOT EXISTS join_date         DATE DEFAULT CURRENT_DATE;
ALTER TABLE leads     ADD COLUMN IF NOT EXISTS type              TEXT;
ALTER TABLE leads     ADD COLUMN IF NOT EXISTS utm_medium        TEXT;
ALTER TABLE leads     ADD COLUMN IF NOT EXISTS internal_memo     TEXT;
ALTER TABLE leads     ADD COLUMN IF NOT EXISTS done_at           TIMESTAMPTZ;
ALTER TABLE notices   ADD COLUMN IF NOT EXISTS popup             BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE notices   ADD COLUMN IF NOT EXISTS author_id         TEXT;
ALTER TABLE history_logs ADD COLUMN IF NOT EXISTS detail         JSONB NOT NULL DEFAULT '{}'::JSONB;

-- premium 타입 보정 (BIGINT → NUMERIC, 이미 NUMERIC이면 무시)
DO $$
BEGIN
  IF EXISTS (SELECT 1 FROM information_schema.columns
    WHERE table_name='customers' AND column_name='premium' AND data_type='bigint')
  THEN ALTER TABLE customers ALTER COLUMN premium TYPE NUMERIC(15,0) USING premium::NUMERIC; END IF;
  IF EXISTS (SELECT 1 FROM information_schema.columns
    WHERE table_name='contracts' AND column_name='premium' AND data_type='bigint')
  THEN ALTER TABLE contracts ALTER COLUMN premium TYPE NUMERIC(15,0) USING premium::NUMERIC; END IF;
END; $$;

-- [STEP 3] 검색 인덱스 ──────────────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_cust_car_number   ON customers (car_number)     WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_cust_phone        ON customers (phone)           WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_cust_expiry       ON customers (expiry_date)     WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_cust_assigned     ON customers (assigned_staff)  WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_cust_status       ON customers (status)          WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_cust_name         ON customers (name)            WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_cust_created      ON customers (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ct_car_number     ON contracts (car_number)      WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_ct_expiry         ON contracts (expiry_date)     WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_ct_customer_id    ON contracts (customer_id)     WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_ct_assigned_to    ON contracts (assigned_to)     WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_ct_renewal_status ON contracts (renewal_status)  WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_ct_policy_number  ON contracts (policy_number)   WHERE deleted=false AND policy_number IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_ct_insurer        ON contracts (insurer)         WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_ct_created        ON contracts (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_pt_status         ON partners (status)           WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_pt_type           ON partners (type)             WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_leads_status      ON leads (status)              WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_leads_phone       ON leads (phone)               WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_leads_created     ON leads (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_leads_assigned    ON leads (assigned_to)         WHERE deleted=false;
CREATE INDEX IF NOT EXISTS idx_hl_target         ON history_logs (target_id, target_type);
CREATE INDEX IF NOT EXISTS idx_hl_created        ON history_logs (created_at DESC);

-- [STEP 4] updated_at 자동 갱신 트리거 ─────────────────────────
CREATE OR REPLACE FUNCTION fn_set_updated_at()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN NEW.updated_at = now(); RETURN NEW; END; $$;

DO $$
DECLARE tbl TEXT;
BEGIN
  FOREACH tbl IN ARRAY ARRAY['staff','customers','contracts','partners','leads','notices']
  LOOP
    IF NOT EXISTS (
      SELECT 1 FROM pg_trigger
      WHERE tgname = 'trg_'||tbl||'_updated_at' AND tgrelid = tbl::regclass
    ) THEN
      EXECUTE format(
        'CREATE TRIGGER trg_%I_updated_at BEFORE UPDATE ON %I FOR EACH ROW EXECUTE FUNCTION fn_set_updated_at()',
        tbl, tbl
      );
    END IF;
  END LOOP;
END; $$;

-- [STEP 5] settings 테이블 (IP허용목록·전사설정 공유) ─────────────
CREATE TABLE IF NOT EXISTS settings (
  id          UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
  key         TEXT        NOT NULL UNIQUE,
  value       JSONB       NOT NULL DEFAULT 'null'::JSONB,
  updated_by  TEXT,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
ALTER TABLE settings ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "settings_anon_all" ON settings;
CREATE POLICY "settings_anon_all" ON settings FOR ALL TO anon USING (true) WITH CHECK (true);
DO $$
BEGIN
  IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname='trg_settings_updated_at' AND tgrelid='settings'::regclass) THEN
    CREATE TRIGGER trg_settings_updated_at BEFORE UPDATE ON settings FOR EACH ROW EXECUTE FUNCTION fn_set_updated_at();
  END IF;
END; $$;
INSERT INTO settings (key, value) VALUES
  ('ip_restrict_enabled', 'false'::JSONB),
  ('ip_allow_list',       '[]'::JSONB),
  ('sheets_url',          'null'::JSONB)
ON CONFLICT (key) DO NOTHING;

-- [STEP 6] RLS 정책 ──────────────────────────────────────────────
ALTER TABLE staff        ENABLE ROW LEVEL SECURITY;
ALTER TABLE customers    ENABLE ROW LEVEL SECURITY;
ALTER TABLE contracts    ENABLE ROW LEVEL SECURITY;
ALTER TABLE partners     ENABLE ROW LEVEL SECURITY;
ALTER TABLE leads        ENABLE ROW LEVEL SECURITY;
ALTER TABLE notices      ENABLE ROW LEVEL SECURITY;
ALTER TABLE history_logs ENABLE ROW LEVEL SECURITY;

DO $$
DECLARE r RECORD;
BEGIN
  FOR r IN SELECT policyname, tablename FROM pg_policies
    WHERE schemaname='public'
      AND tablename IN ('staff','customers','contracts','partners','leads','notices','history_logs')
  LOOP
    EXECUTE format('DROP POLICY IF EXISTS %I ON %I', r.policyname, r.tablename);
  END LOOP;
END; $$;

-- staff: SELECT·INSERT·UPDATE 허용 (로그인 검증 + 직원 관리)
CREATE POLICY "staff_anon_select" ON staff FOR SELECT TO anon USING (true);
CREATE POLICY "staff_anon_insert" ON staff FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "staff_anon_update" ON staff FOR UPDATE TO anon USING (true) WITH CHECK (true);
-- customers · contracts · partners · notices: 전체 허용
CREATE POLICY "customers_anon_all"  ON customers    FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "contracts_anon_all"  ON contracts    FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "partners_anon_all"   ON partners     FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "notices_anon_all"    ON notices      FOR ALL TO anon USING (true) WITH CHECK (true);
-- leads: 웹(anon) INSERT + 직원 SELECT·UPDATE·DELETE
CREATE POLICY "leads_anon_insert"   ON leads FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "leads_anon_select"   ON leads FOR SELECT TO anon USING (true);
CREATE POLICY "leads_anon_update"   ON leads FOR UPDATE TO anon USING (true) WITH CHECK (true);
CREATE POLICY "leads_anon_delete"   ON leads FOR DELETE TO anon USING (true);
CREATE POLICY "leads_auth_all"      ON leads FOR ALL  TO authenticated USING (true) WITH CHECK (true);
-- history_logs: INSERT·SELECT만 허용 (불변 로그)
CREATE POLICY "history_logs_anon_insert" ON history_logs FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "history_logs_anon_select" ON history_logs FOR SELECT TO anon USING (true);

-- [STEP 7] Realtime 구독 ─────────────────────────────────────────
DO $$
DECLARE tbl TEXT;
BEGIN
  FOREACH tbl IN ARRAY ARRAY['leads','customers','contracts']
  LOOP
    BEGIN
      EXECUTE format('ALTER PUBLICATION supabase_realtime ADD TABLE %I', tbl);
    EXCEPTION WHEN duplicate_object THEN NULL;
    END;
  END LOOP;
END; $$;

-- [STEP 8] 직원 계정 초기화 ──────────────────────────────────────
INSERT INTO staff (staff_id, password, name, role, dept, title, active, permissions) VALUES
  ('speed',     'dlscfj22',     '신인철', 'admin',     '관리', '대표',  true, '["notice","user_manage","partner_approve"]'::JSONB),
  ('secretary', 'secretary123', '이윤미', 'secretary', '관리', '실장',  true, '["notice","user_manage"]'::JSONB),
  ('staff1',    'staff123',     '박승홍', 'staff',     '영업', '부장',  true, '[]'::JSONB),
  ('staff2',    'staff1234',    '박미래', 'staff',     '영업', '과장',  true, '[]'::JSONB)
ON CONFLICT (staff_id) DO UPDATE SET
  password=EXCLUDED.password, name=EXCLUDED.name,
  role=EXCLUDED.role, dept=EXCLUDED.dept, title=EXCLUDED.title,
  active=EXCLUDED.active, updated_at=now();

-- [STEP 8] 완료 확인 ─────────────────────────────────────────────
SELECT
  t.table_name AS "테이블",
  pg_size_pretty(pg_total_relation_size(t.table_name::regclass)) AS "크기",
  (XPATH('/row/c/text()',
    query_to_xml(format('SELECT count(*) AS c FROM %I', t.table_name), false, true, '')
  ))[1]::TEXT::BIGINT AS "전체 행 수"
FROM (
  VALUES ('staff'),('customers'),('contracts'),
         ('partners'),('leads'),('notices'),('history_logs')
) AS t(table_name)
ORDER BY t.table_name;
▶ STEP 4 — Run 버튼 클릭해서 실행
1

SQL Editor 우측 상단 ▶ Run 버튼 클릭

또는 단축키 Ctrl + Enter (Mac: Cmd+Enter)

SQL Editor — 실행 결과
실행 결과 예시:
✅ 성공 (정상)
Success. No rows returned
⚠️ 이 오류는 정상 (이미 테이블 존재)
ERROR: relation "staff" already exists
→ CREATE TABLE IF NOT EXISTS 이므로 건너뜀, 나머지 실행됨
❌ 실제 오류 (조치 필요)
permission denied for table ...
→ 아래 "오류 해결" 섹션 참고
2

Table Editor에서 테이블 생성 확인

왼쪽 메뉴 📋 Table Editor 클릭 → 아래 테이블들이 보이면 성공입니다:

staff customers contracts partners leads notices history_logs
🔗 STEP 5 — 연결 테스트
1

아래 버튼을 클릭해 연결을 확인하세요

SQL 실행 후 직원 4명이 나오면 완전히 성공입니다.

🔄 Supabase 프로젝트에 직접 연결하여 직원 데이터를 조회합니다.
🚨 다른 PC에서 로그인 안 될 때 — RLS 정책 재적용
⚠️ 증상
  • 이 PC에서는 로그인 정상 — 다른 PC에서는 로그인 후 데이터 0건
  • 브라우저 콘솔(F12)에 401 Unauthorized 오류 표시
  • 대시보드/고객/계약 화면이 비어 있음
✅ 해결 방법 — Supabase SQL Editor에서 RLS 정책 재적용
아래 SQL을 Supabase → SQL Editor에 붙여넣고 실행(F5)하면 anon 키 접근 권한이 복구됩니다.
🚀 SQL Editor 바로 열기
-- 인 다이렉트 카보험 — RLS anon 정책 재적용
-- 다른 PC에서 401 오류 발생 시 이 SQL을 실행하세요.

ALTER TABLE staff        ENABLE ROW LEVEL SECURITY;
ALTER TABLE customers    ENABLE ROW LEVEL SECURITY;
ALTER TABLE contracts    ENABLE ROW LEVEL SECURITY;
ALTER TABLE partners     ENABLE ROW LEVEL SECURITY;
ALTER TABLE leads        ENABLE ROW LEVEL SECURITY;
ALTER TABLE notices      ENABLE ROW LEVEL SECURITY;
ALTER TABLE history_logs ENABLE ROW LEVEL SECURITY;
ALTER TABLE settings     ENABLE ROW LEVEL SECURITY;

DO $$
DECLARE r RECORD;
BEGIN
  FOR r IN
    SELECT policyname, tablename FROM pg_policies
    WHERE schemaname = 'public'
      AND tablename IN ('staff','customers','contracts','partners',
                        'leads','notices','history_logs','settings')
  LOOP
    EXECUTE format('DROP POLICY IF EXISTS %I ON %I', r.policyname, r.tablename);
  END LOOP;
END;
$$;

CREATE POLICY "staff_anon_select" ON staff FOR SELECT TO anon USING (true);
CREATE POLICY "staff_anon_insert" ON staff FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "staff_anon_update" ON staff FOR UPDATE TO anon USING (true) WITH CHECK (true);
CREATE POLICY "customers_anon_all" ON customers FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "contracts_anon_all" ON contracts FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "partners_anon_all"  ON partners  FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "leads_anon_all"     ON leads     FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "notices_anon_all"   ON notices   FOR ALL TO anon USING (true) WITH CHECK (true);
CREATE POLICY "history_logs_anon_insert" ON history_logs FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "history_logs_anon_select" ON history_logs FOR SELECT TO anon USING (true);
CREATE POLICY "settings_anon_all"  ON settings  FOR ALL TO anon USING (true) WITH CHECK (true);

SELECT tablename, policyname, cmd FROM pg_policies
WHERE schemaname='public' ORDER BY tablename, policyname;
🔧 오류 해결 가이드
오류 1: 테이블이 이미 존재할 때
ERROR: relation "staff" already exists
정상입니다. CREATE TABLE IF NOT EXISTS 구문이므로 이미 있으면 건너뜁니다.
나머지 INSERT, RLS, Realtime 구문은 정상 실행됩니다. 그대로 진행하세요.
오류 2: RLS 정책이 이미 있을 때
ERROR: policy "allow_all" for table "staff" already exists
정상입니다. SQL에 IF NOT EXISTS 조건이 포함되어 있어 중복 생성을 방지합니다.
오류처럼 보여도 전체 실행이 완료됩니다.
오류 3: 권한 오류
permission denied for table ... / must be owner of table ...
프로젝트 소유자가 아닌 계정으로 로그인한 경우입니다.
→ Supabase에서 로그아웃 후 프로젝트를 만든 본인 계정으로 다시 로그인하세요.
오류 4: Realtime 오류
ERROR: publication "supabase_realtime" does not exist
⚠️ Realtime 기능 미활성화 상태입니다.
→ Supabase 대시보드 → Database → Replication → Realtime을 활성화한 후 마지막 3줄만 다시 실행하세요.
오류 5: 연결 테스트 실패 (직원 0명)
연결 성공이지만 직원 0명 (테이블이 비어 있음)
⚠️ INSERT INTO staff 부분만 다시 실행해 보세요:
INSERT INTO staff (staff_id, password, name, role, dept, title, active) VALUES
  ('admin',      'admin123',  '대표자',   'admin',     '경영',   '대표',    true),
  ('secretary1', 'sec123',    '이비서',   'secretary', '관리',   '비서',    true),
  ('staff1',     'staff123',  '김직원',   'staff',     '영업1팀','팀장',    true),
  ('staff2',     'staff123',  '박직원',   'staff',     '영업2팀','사원',    true)
ON CONFLICT (staff_id) DO NOTHING;
🔗 직원별 전용 접속 URL

각 직원에게 아래 URL을 카카오톡 또는 문자로 공유하세요.
링크를 클릭하면 아이디가 자동 입력되고, 비밀번호만 입력하면 바로 로그인됩니다.

⚠️ 비밀번호는 처음 로그인 후 직원에게 변경하도록 안내하세요.
기본 비밀번호: admin → admin123 / secretary1 → sec123 / staff1,2 → staff123
🚀 설정 완료 후 다음 단계
🔐
직원 로그인
admin / admin123
📊
대시보드
로그인 후 이동
📋
Google Sheets 연동
데이터 백업·공유
⚙️
시스템 설정
직원 정보·연락처
✅ SQL 실행 완료 후 staff-login.html에서 admin / admin123 으로 로그인하면
Supabase DB와 연결된 직원 전산을 모든 장소에서 사용할 수 있습니다.