Allowed conversion w/o homeowner

X-Lovable-Edit-ID: edt-5515d6e7-3436-4d95-84d1-61061dff8b22
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 07:34:39 +00:00
co-authored by renee-png
4 changed files with 59 additions and 14 deletions
@@ -56,12 +56,16 @@ export function ConvertToCollectionsDialog({
const [selected, setSelected] = useState<Set<string>>(new Set());
const [search, setSearch] = useState("");
const [status, setStatus] = useState("none");
const [includeEmpty, setIncludeEmpty] = useState(false);
const [emptyName, setEmptyName] = useState("");
useEffect(() => {
if (!open) return;
setSelected(new Set());
setSearch("");
setStatus("none");
setIncludeEmpty(false);
setEmptyName("");
(async () => {
setLoading(true);
const [{ data: hos }, { data: cols }] = await Promise.all([
@@ -77,7 +81,9 @@ export function ConvertToCollectionsDialog({
.eq("case_id", caseId),
]);
setHomeowners(hos ?? []);
setExistingIds(new Set((cols ?? []).map((c) => c.homeowner_id)));
setExistingIds(
new Set((cols ?? []).map((c) => c.homeowner_id).filter((id): id is string => !!id)),
);
setLoading(false);
})();
}, [open, caseId, clientId]);
@@ -123,17 +129,26 @@ export function ConvertToCollectionsDialog({
};
const submit = async () => {
if (selected.size === 0) {
toast.error("Pick at least one homeowner");
if (selected.size === 0 && !includeEmpty) {
toast.error("Pick at least one homeowner or enable an empty collection");
return;
}
setSubmitting(true);
const rows = Array.from(selected).map((homeowner_id) => ({
const rows: any[] = Array.from(selected).map((homeowner_id) => ({
case_id: caseId,
homeowner_id,
status,
created_by: user?.id,
}));
if (includeEmpty) {
rows.push({
case_id: caseId,
homeowner_id: null,
status,
name: emptyName.trim() || null,
created_by: user?.id,
});
}
const { error } = await supabase.from("collections").insert(rows);
setSubmitting(false);
if (error) {
@@ -252,15 +267,36 @@ export function ConvertToCollectionsDialog({
</>
)}
</div>
<div className="border rounded-md p-3 space-y-2 bg-muted/20">
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox
checked={includeEmpty}
onCheckedChange={(v) => setIncludeEmpty(!!v)}
/>
<span>Also create one collection without an attached homeowner</span>
</label>
{includeEmpty && (
<Input
placeholder="Optional name (e.g. Unassigned ledger)"
value={emptyName}
onChange={(e) => setEmptyName(e.target.value)}
/>
)}
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button onClick={submit} disabled={submitting || selected.size === 0}>
<Button
onClick={submit}
disabled={submitting || (selected.size === 0 && !includeEmpty)}
>
{submitting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
Create {selected.size > 0 ? selected.size : ""} collection{selected.size === 1 ? "" : "s"}
Create {selected.size + (includeEmpty ? 1 : 0)} collection
{selected.size + (includeEmpty ? 1 : 0) === 1 ? "" : "s"}
</Button>
</DialogFooter>
</DialogContent>
+3 -3
View File
@@ -742,7 +742,7 @@ export type Database = {
created_at: string
created_by: string | null
current_stage: string | null
homeowner_id: string
homeowner_id: string | null
id: string
interest_rate_override: number | null
name: string | null
@@ -757,7 +757,7 @@ export type Database = {
created_at?: string
created_by?: string | null
current_stage?: string | null
homeowner_id: string
homeowner_id?: string | null
id?: string
interest_rate_override?: number | null
name?: string | null
@@ -772,7 +772,7 @@ export type Database = {
created_at?: string
created_by?: string | null
current_stage?: string | null
homeowner_id?: string
homeowner_id?: string | null
id?: string
interest_rate_override?: number | null
name?: string | null
+13 -5
View File
@@ -56,7 +56,7 @@ function CollectionsIndexPage() {
supabase
.from("collections")
.select(
"id, status, current_stage, opened_at, homeowner:homeowners(id, first_name, last_name, unit_number, opening_balance), case:cases(id, case_number, title, client:clients(id, name))",
"id, status, current_stage, opened_at, name, homeowner:homeowners(id, first_name, last_name, unit_number, opening_balance), case:cases(id, case_number, title, client:clients(id, name))",
)
.order("created_at", { ascending: false }),
supabase
@@ -234,10 +234,18 @@ function CollectionsIndexPage() {
params={{ collectionId: r.id }}
className="hover:underline"
>
{r.homeowner?.last_name}, {r.homeowner?.first_name}
{r.homeowner?.unit_number && (
<span className="text-muted-foreground font-normal ml-1">
· Unit {r.homeowner.unit_number}
{r.homeowner ? (
<>
{r.homeowner.last_name}, {r.homeowner.first_name}
{r.homeowner.unit_number && (
<span className="text-muted-foreground font-normal ml-1">
· Unit {r.homeowner.unit_number}
</span>
)}
</>
) : (
<span className="italic text-muted-foreground">
{(r as any).name || "Unassigned"}
</span>
)}
</Link>
@@ -0,0 +1 @@
ALTER TABLE public.collections ALTER COLUMN homeowner_id DROP NOT NULL;