diff --git a/app/indaba/(ops)/map/page.tsx b/app/indaba/(ops)/map/page.tsx
index 123d37c..b6bcbfe 100644
--- a/app/indaba/(ops)/map/page.tsx
+++ b/app/indaba/(ops)/map/page.tsx
@@ -64,7 +64,7 @@ export default async function MapPage() {
supabase
.from("businesses")
.select(
- "id, name, sector, zone_id, lat, lng, est_monthly_volume, notes, decision_maker_name, decision_maker_title, phone, email, linkedin, key_suppliers, key_customers, pain_points, zimx_fit_score, active, mapped_by, created_at",
+ "id, name, sector, zone_id, lat, lng, est_monthly_volume, notes, decision_maker_name, decision_maker_title, phone, email, linkedin, key_suppliers, key_customers, pain_points, zimx_fit_score, active, mapped_by, created_at, photos",
),
supabase
.from("supply_chain_links")
@@ -137,6 +137,7 @@ export default async function MapPage() {
active: Boolean(b.active ?? true),
mapped_by: b.mapped_by ?? null,
created_at: String(b.created_at ?? new Date().toISOString()),
+ photos: b.photos ?? [],
}));
const links: MapLink[] = linkRows.map((link) => {
diff --git a/components/ops/Map/BulawayoMap.tsx b/components/ops/Map/BulawayoMap.tsx
index 115bfac..3008236 100644
--- a/components/ops/Map/BulawayoMap.tsx
+++ b/components/ops/Map/BulawayoMap.tsx
@@ -255,11 +255,13 @@ export default function BulawayoMap({
b.notes && b.notes.length > 120
? `${b.notes.slice(0, 120)}…`
: b.notes ?? "";
+ const firstPhoto = b.photos?.[0] ?? null;
const popupHtml = `
${escapeHtml(b.name)}
${escapeHtml(b.sector)} · ${escapeHtml(zoneName)}
+ ${firstPhoto ? `
})
` : `
📷 Tap to add photo
`}
$${volume.toLocaleString()}/mo
${notes ? `
${escapeHtml(notes)}
` : ""}
diff --git a/components/ops/Map/MapView.tsx b/components/ops/Map/MapView.tsx
index 6afcd70..29d33ed 100644
--- a/components/ops/Map/MapView.tsx
+++ b/components/ops/Map/MapView.tsx
@@ -12,6 +12,7 @@ import Textarea from "@/components/ops/ui/Textarea";
import Button from "@/components/ops/ui/Button";
import { useToast } from "@/components/ui/Toast";
import { opsApiPost } from "@/lib/ops/api-client";
+import { createSupabaseBrowserClient } from "@/lib/supabase/client";
import AddBusinessDialog from "./AddBusinessDialog";
import LogInteractionModal from "@/components/ops/Interactions/LogInteractionModal";
@@ -66,6 +67,8 @@ export default function MapView({
const [selected, setSelected] = useState
(null);
const [movingPinId, setMovingPinId] = useState(null);
const [editing, setEditing] = useState(false);
+ const [isUploadingPhoto, setIsUploadingPhoto] = useState(false);
+ const [uploadSuccess, setUploadSuccess] = useState(false);
const [form, setForm] = useState>({});
const [interactionBusinessId, setInteractionBusinessId] = useState(null);
const [showCandidates, setShowCandidates] = useState(true);
@@ -207,6 +210,50 @@ export default function MapView({
});
}
+ async function handlePhotoUpload(file: File) {
+ if (!selected || isUploadingPhoto) return;
+ setIsUploadingPhoto(true);
+ const supabase = createSupabaseBrowserClient();
+ const filePath = `${selected.id}/${Date.now()}_${file.name.replace(/\s+/g, "_")}`;
+ const { error: uploadError } = await supabase.storage
+ .from("business-photos")
+ .upload(filePath, file, { upsert: false });
+ if (uploadError) {
+ toast.error("Upload failed. Please try again.");
+ setIsUploadingPhoto(false);
+ return;
+ }
+ const { data } = supabase.storage.from("business-photos").getPublicUrl(filePath);
+ const nextPhotos = [...(selected.photos ?? []), data.publicUrl];
+ const { error: updateError } = await supabase
+ .from("businesses")
+ .update({ photos: nextPhotos })
+ .eq("id", selected.id);
+ if (updateError) {
+ toast.error("Photo uploaded, but saving failed.");
+ setIsUploadingPhoto(false);
+ return;
+ }
+ setItems((prev) => prev.map((b) => (b.id === selected.id ? { ...b, photos: nextPhotos } : b)));
+ setSelected((prev) => (prev ? { ...prev, photos: nextPhotos } : prev));
+ setUploadSuccess(true);
+ window.setTimeout(() => setUploadSuccess(false), 1200);
+ setIsUploadingPhoto(false);
+ }
+
+ async function handleDeletePhoto(photoUrl: string) {
+ if (!selected) return;
+ const nextPhotos = (selected.photos ?? []).filter((url) => url !== photoUrl);
+ const supabase = createSupabaseBrowserClient();
+ const { error } = await supabase.from("businesses").update({ photos: nextPhotos }).eq("id", selected.id);
+ if (error) {
+ toast.error("Could not delete photo.");
+ return;
+ }
+ setItems((prev) => prev.map((b) => (b.id === selected.id ? { ...b, photos: nextPhotos } : b)));
+ setSelected((prev) => (prev ? { ...prev, photos: nextPhotos } : prev));
+ }
+
return (
@@ -373,6 +420,31 @@ export default function MapView({
))}
+
+
Photos
+
+ {(selected.photos ?? []).map((photo) => (
+
+

+
+
+ ))}
+
+
+