mirror of
https://github.com/FranP-code/inbox-negotiator.git
synced 2025-10-13 00:42:26 +00:00
Implement debt variable management with database integration in DebtCard component
This commit is contained in:
@@ -27,8 +27,8 @@ import {
|
|||||||
TrendingUp,
|
TrendingUp,
|
||||||
Edit3,
|
Edit3,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { supabase } from "../lib/supabase";
|
import { supabase, type Debt, type DebtVariable } from "../lib/supabase";
|
||||||
import type { Debt } from "../lib/supabase";
|
import { toast } from "../hooks/use-toast";
|
||||||
|
|
||||||
interface DebtCardProps {
|
interface DebtCardProps {
|
||||||
debt: Debt;
|
debt: Debt;
|
||||||
@@ -110,22 +110,82 @@ export function DebtCard({ debt, onUpdate }: DebtCardProps) {
|
|||||||
const [body, setBody] = useState("");
|
const [body, setBody] = useState("");
|
||||||
const [variables, setVariables] = useState<Record<string, string>>({});
|
const [variables, setVariables] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
|
// 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<string, string> = {};
|
||||||
|
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<string, string>) => {
|
||||||
|
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
|
// Initialize data when dialog opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debt.metadata?.aiEmail) {
|
const initializeData = async () => {
|
||||||
const aiEmail = debt.metadata.aiEmail;
|
if (debt.metadata?.aiEmail) {
|
||||||
setSubject(aiEmail.subject || "");
|
const aiEmail = debt.metadata.aiEmail;
|
||||||
setBody(aiEmail.body || "");
|
setSubject(aiEmail.subject || "");
|
||||||
|
setBody(aiEmail.body || "");
|
||||||
|
|
||||||
// Extract variables from both subject and body
|
// Extract variables from both subject and body
|
||||||
const allText = `${aiEmail.subject || ""} ${aiEmail.body || ""}`;
|
const allText = `${aiEmail.subject || ""} ${aiEmail.body || ""}`;
|
||||||
const extractedVars = extractVariables(allText);
|
const extractedVars = extractVariables(allText);
|
||||||
const initialVariables: Record<string, string> = {};
|
|
||||||
extractedVars.forEach((variable) => {
|
// Load saved variables from database
|
||||||
initialVariables[variable] = "";
|
const savedVariables = await loadVariables();
|
||||||
});
|
|
||||||
setVariables(initialVariables);
|
// Merge extracted variables with saved values
|
||||||
}
|
const initialVariables: Record<string, string> = {};
|
||||||
|
extractedVars.forEach((variable) => {
|
||||||
|
initialVariables[variable] = savedVariables[variable] || "";
|
||||||
|
});
|
||||||
|
|
||||||
|
setVariables(initialVariables);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeData();
|
||||||
}, [debt.metadata?.aiEmail]);
|
}, [debt.metadata?.aiEmail]);
|
||||||
|
|
||||||
// Update variables when body changes
|
// Update variables when body changes
|
||||||
@@ -223,11 +283,24 @@ export function DebtCard({ debt, onUpdate }: DebtCardProps) {
|
|||||||
.eq("id", debt.id);
|
.eq("id", debt.id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error saving changes:", error);
|
console.error("Error saving debt metadata:", error);
|
||||||
// You could add toast notification here
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Failed to save email changes. Please try again.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
return;
|
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
|
// Call onUpdate callback to refresh the parent component
|
||||||
if (onUpdate) {
|
if (onUpdate) {
|
||||||
onUpdate();
|
onUpdate();
|
||||||
@@ -236,6 +309,11 @@ export function DebtCard({ debt, onUpdate }: DebtCardProps) {
|
|||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving changes:", error);
|
console.error("Error saving changes:", error);
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Failed to save changes. Please try again.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsSaving(false);
|
setIsSaving(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL;
|
||||||
const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
|
const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY;
|
||||||
|
|
||||||
if (!supabaseUrl || !supabaseAnonKey) {
|
if (!supabaseUrl || !supabaseAnonKey) {
|
||||||
throw new Error('Missing Supabase environment variables');
|
throw new Error("Missing Supabase environment variables");
|
||||||
}
|
}
|
||||||
|
|
||||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
||||||
@@ -22,7 +22,7 @@ export type Debt = {
|
|||||||
vendor: string;
|
vendor: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
raw_email: string | null;
|
raw_email: string | null;
|
||||||
status: 'received' | 'negotiating' | 'settled' | 'failed' | 'opted_out';
|
status: "received" | "negotiating" | "settled" | "failed" | "opted_out";
|
||||||
negotiated_plan: string | null;
|
negotiated_plan: string | null;
|
||||||
projected_savings: number;
|
projected_savings: number;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
@@ -67,3 +67,12 @@ export type EmailProcessingUsage = {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DebtVariable = {
|
||||||
|
id: string;
|
||||||
|
debt_id: string;
|
||||||
|
variable_name: string;
|
||||||
|
variable_value: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
};
|
||||||
|
|||||||
53
supabase/migrations/20250607008000_add_debt_variables.sql
Normal file
53
supabase/migrations/20250607008000_add_debt_variables.sql
Normal file
@@ -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();
|
||||||
Reference in New Issue
Block a user