From 7f70ec880f730c0967bf088222e50bd931e29b3e Mon Sep 17 00:00:00 2001 From: Francisco Pessano Date: Sat, 7 Jun 2025 14:53:02 -0300 Subject: [PATCH] Implement debt variable management with database integration in DebtCard component --- src/components/DebtCard.tsx | 112 +++++++++++++++--- src/lib/supabase.ts | 17 ++- .../20250607008000_add_debt_variables.sql | 53 +++++++++ 3 files changed, 161 insertions(+), 21 deletions(-) create mode 100644 supabase/migrations/20250607008000_add_debt_variables.sql diff --git a/src/components/DebtCard.tsx b/src/components/DebtCard.tsx index ad7218a..62d3df1 100644 --- a/src/components/DebtCard.tsx +++ b/src/components/DebtCard.tsx @@ -27,8 +27,8 @@ import { TrendingUp, Edit3, } from "lucide-react"; -import { supabase } from "../lib/supabase"; -import type { Debt } from "../lib/supabase"; +import { supabase, type Debt, type DebtVariable } from "../lib/supabase"; +import { toast } from "../hooks/use-toast"; interface DebtCardProps { debt: Debt; @@ -110,22 +110,82 @@ export function DebtCard({ debt, onUpdate }: DebtCardProps) { const [body, setBody] = useState(""); const [variables, setVariables] = useState>({}); + // Load variables from database + const loadVariables = async () => { + try { + const { data: dbVariables, error } = await supabase + .from("debt_variables") + .select("variable_name, variable_value") + .eq("debt_id", debt.id); + + if (error) throw error; + + const loadedVariables: Record = {}; + dbVariables?.forEach((dbVar) => { + loadedVariables[dbVar.variable_name] = dbVar.variable_value || ""; + }); + + return loadedVariables; + } catch (error) { + console.error("Error loading variables:", error); + return {}; + } + }; + + // Save variables to database + const saveVariables = async (variablesToSave: Record) => { + try { + // First, delete existing variables for this debt + await supabase.from("debt_variables").delete().eq("debt_id", debt.id); + + // Then insert new variables + const variableRecords = Object.entries(variablesToSave).map( + ([name, value]) => ({ + debt_id: debt.id, + variable_name: name, + variable_value: value, + }) + ); + + if (variableRecords.length > 0) { + const { error } = await supabase + .from("debt_variables") + .insert(variableRecords); + + if (error) throw error; + } + } catch (error) { + console.error("Error saving variables:", error); + throw error; + } + }; + // Initialize data when dialog opens useEffect(() => { - if (debt.metadata?.aiEmail) { - const aiEmail = debt.metadata.aiEmail; - setSubject(aiEmail.subject || ""); - setBody(aiEmail.body || ""); + const initializeData = async () => { + if (debt.metadata?.aiEmail) { + const aiEmail = debt.metadata.aiEmail; + setSubject(aiEmail.subject || ""); + setBody(aiEmail.body || ""); - // Extract variables from both subject and body - const allText = `${aiEmail.subject || ""} ${aiEmail.body || ""}`; - const extractedVars = extractVariables(allText); - const initialVariables: Record = {}; - extractedVars.forEach((variable) => { - initialVariables[variable] = ""; - }); - setVariables(initialVariables); - } + // Extract variables from both subject and body + const allText = `${aiEmail.subject || ""} ${aiEmail.body || ""}`; + const extractedVars = extractVariables(allText); + + // Load saved variables from database + const savedVariables = await loadVariables(); + + // Merge extracted variables with saved values + const initialVariables: Record = {}; + extractedVars.forEach((variable) => { + initialVariables[variable] = savedVariables[variable] || ""; + }); + + setVariables(initialVariables); + } + }; + + initializeData(); }, [debt.metadata?.aiEmail]); // Update variables when body changes @@ -223,11 +283,24 @@ export function DebtCard({ debt, onUpdate }: DebtCardProps) { .eq("id", debt.id); if (error) { - console.error("Error saving changes:", error); - // You could add toast notification here + console.error("Error saving debt metadata:", error); + toast({ + title: "Error", + description: "Failed to save email changes. Please try again.", + variant: "destructive", + }); return; } + // Save variables to database + await saveVariables(variables); + + toast({ + title: "Changes saved", + description: + "Your email and variables have been updated successfully.", + }); + // Call onUpdate callback to refresh the parent component if (onUpdate) { onUpdate(); @@ -236,6 +309,11 @@ export function DebtCard({ debt, onUpdate }: DebtCardProps) { setIsEditing(false); } catch (error) { console.error("Error saving changes:", error); + toast({ + title: "Error", + description: "Failed to save changes. Please try again.", + variant: "destructive", + }); } finally { setIsSaving(false); } diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index 75df668..fb8e685 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -1,10 +1,10 @@ -import { createClient } from '@supabase/supabase-js'; +import { createClient } from "@supabase/supabase-js"; const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL; const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY; if (!supabaseUrl || !supabaseAnonKey) { - throw new Error('Missing Supabase environment variables'); + throw new Error("Missing Supabase environment variables"); } export const supabase = createClient(supabaseUrl, supabaseAnonKey); @@ -22,7 +22,7 @@ export type Debt = { vendor: string; amount: number; raw_email: string | null; - status: 'received' | 'negotiating' | 'settled' | 'failed' | 'opted_out'; + status: "received" | "negotiating" | "settled" | "failed" | "opted_out"; negotiated_plan: string | null; projected_savings: number; user_id: string; @@ -66,4 +66,13 @@ export type EmailProcessingUsage = { emails_processed: number; created_at: string; updated_at: string; -}; \ No newline at end of file +}; + +export type DebtVariable = { + id: string; + debt_id: string; + variable_name: string; + variable_value: string | null; + created_at: string; + updated_at: string; +}; diff --git a/supabase/migrations/20250607008000_add_debt_variables.sql b/supabase/migrations/20250607008000_add_debt_variables.sql new file mode 100644 index 0000000..151e0b5 --- /dev/null +++ b/supabase/migrations/20250607008000_add_debt_variables.sql @@ -0,0 +1,53 @@ +-- Create debt_variables table to store variable values for each debt +CREATE TABLE IF NOT EXISTS debt_variables ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + debt_id uuid REFERENCES debts(id) ON DELETE CASCADE NOT NULL, + variable_name text NOT NULL, + variable_value text, + created_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now(), + UNIQUE(debt_id, variable_name) +); + +-- Enable RLS +ALTER TABLE debt_variables ENABLE ROW LEVEL SECURITY; + +-- Create policies for debt_variables +CREATE POLICY "Users can manage their debt variables" + ON debt_variables + FOR ALL + TO authenticated + USING ( + EXISTS ( + SELECT 1 FROM debts + WHERE debts.id = debt_variables.debt_id + AND debts.user_id = auth.uid() + ) + ) + WITH CHECK ( + EXISTS ( + SELECT 1 FROM debts + WHERE debts.id = debt_variables.debt_id + AND debts.user_id = auth.uid() + ) + ); + +-- Create indexes for performance +CREATE INDEX IF NOT EXISTS idx_debt_variables_debt_id ON debt_variables(debt_id); +CREATE INDEX IF NOT EXISTS idx_debt_variables_name ON debt_variables(variable_name); + +-- Create function to update updated_at timestamp +CREATE OR REPLACE FUNCTION update_debt_variables_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$ language 'plpgsql'; + +-- Create trigger for updated_at +DROP TRIGGER IF EXISTS update_debt_variables_updated_at ON debt_variables; +CREATE TRIGGER update_debt_variables_updated_at + BEFORE UPDATE ON debt_variables + FOR EACH ROW + EXECUTE FUNCTION update_debt_variables_updated_at_column();