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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user