254 lines
7.2 KiB
TypeScript
254 lines
7.2 KiB
TypeScript
import React, { useState } from "react";
|
|
import { View, ScrollView, TouchableOpacity, Alert } from "react-native";
|
|
import {
|
|
theme,
|
|
AppContainer,
|
|
MyText,
|
|
tw,
|
|
useManualRefresh,
|
|
useMarkDataFetchers,
|
|
MyTouchableOpacity,
|
|
BottomDialog,
|
|
} from "common-ui";
|
|
import { trpc } from "../../../src/trpc-client";
|
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
|
import { LinearGradient } from "expo-linear-gradient";
|
|
import dayjs from "dayjs";
|
|
import { useRouter } from "expo-router";
|
|
|
|
interface ProductGroup {
|
|
id: number;
|
|
groupName: string;
|
|
description: string | null;
|
|
createdAt: string;
|
|
products: any[];
|
|
productCount: number;
|
|
}
|
|
|
|
const GroupItem = ({
|
|
group,
|
|
onEdit,
|
|
onDelete,
|
|
onViewProducts,
|
|
index,
|
|
}: {
|
|
group: ProductGroup;
|
|
onEdit: (group: ProductGroup) => void;
|
|
onDelete: (id: number) => void;
|
|
onViewProducts: (products: any[]) => void;
|
|
index: number;
|
|
}) => {
|
|
return (
|
|
<View style={tw``}>
|
|
<View style={tw`px-2 py-6`}>
|
|
{/* Top Header: Name & Actions */}
|
|
<View style={tw`flex-row justify-between items-start mb-6`}>
|
|
<View style={tw`flex-row items-center flex-1`}>
|
|
<View
|
|
style={tw`w-12 h-12 rounded-2xl bg-brand50 items-center justify-center mr-4`}
|
|
>
|
|
<MaterialIcons
|
|
name="group"
|
|
size={24}
|
|
color={theme.colors.brand600}
|
|
/>
|
|
</View>
|
|
<View style={tw`flex-1`}>
|
|
<MyText
|
|
style={tw`text-slate-400 text-[10px] font-black uppercase tracking-widest`}
|
|
>
|
|
Group Name
|
|
</MyText>
|
|
<MyText
|
|
style={tw`text-xl font-black text-slate-900`}
|
|
numberOfLines={1}
|
|
>
|
|
{group.groupName}
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={tw`flex-row items-center`}>
|
|
<TouchableOpacity
|
|
onPress={() => onEdit(group)}
|
|
style={tw`p-2 mr-2`}
|
|
>
|
|
<MaterialIcons name="edit" size={20} color="#64748B" />
|
|
</TouchableOpacity>
|
|
<TouchableOpacity
|
|
onPress={() => onDelete(group.id)}
|
|
style={tw`p-2`}
|
|
>
|
|
<MaterialIcons name="delete" size={20} color="#EF4444" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Description */}
|
|
{group.description && (
|
|
<View
|
|
style={tw`bg-slate-50 rounded-2xl p-4 mb-6 border border-slate-100`}
|
|
>
|
|
<MyText style={tw`text-slate-700 font-medium`}>
|
|
{group.description}
|
|
</MyText>
|
|
</View>
|
|
)}
|
|
|
|
{/* Stats */}
|
|
<View style={tw`flex-row items-center justify-between`}>
|
|
<View style={tw`flex-row items-center`}>
|
|
<TouchableOpacity
|
|
onPress={() => onViewProducts(group.products)}
|
|
style={tw`flex-row items-center mr-4`}
|
|
>
|
|
<MaterialIcons
|
|
name="inventory"
|
|
size={14}
|
|
color={theme.colors.brand500}
|
|
/>
|
|
<MyText style={tw`text-xs font-bold text-brand500 ml-1.5`}>
|
|
{group.productCount} Products
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
<View style={tw`flex-row items-center`}>
|
|
<MaterialIcons name="schedule" size={14} color="#94A3B8" />
|
|
<MyText style={tw`text-xs font-bold text-slate-500 ml-1.5`}>
|
|
{dayjs(group.createdAt).format("MMM DD, YYYY")}
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default function ProductGroupings() {
|
|
const router = useRouter();
|
|
const {
|
|
data: groupsData,
|
|
isLoading,
|
|
error,
|
|
refetch,
|
|
} = trpc.admin.product.getGroups.useQuery();
|
|
const deleteGroup = trpc.admin.product.deleteGroup.useMutation();
|
|
|
|
const groups = groupsData?.groups || [];
|
|
const [viewProducts, setViewProducts] = useState<any[] | null>(null);
|
|
|
|
useManualRefresh(refetch);
|
|
useMarkDataFetchers(() => {
|
|
refetch();
|
|
});
|
|
|
|
const handleCreate = () => {
|
|
router.push("/(drawer)/create-product-group");
|
|
};
|
|
|
|
const handleEdit = (group: ProductGroup) => {
|
|
router.push(`/(drawer)/edit-product-group/${group.id}`);
|
|
};
|
|
|
|
const handleDelete = (id: number) => {
|
|
Alert.alert("Delete Group", "Are you sure you want to delete this group?", [
|
|
{ text: "Cancel", style: "cancel" },
|
|
{
|
|
text: "Delete",
|
|
style: "destructive",
|
|
onPress: () => {
|
|
deleteGroup.mutate(
|
|
{ id },
|
|
{
|
|
onSuccess: () => {
|
|
refetch();
|
|
},
|
|
}
|
|
);
|
|
},
|
|
},
|
|
]);
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<AppContainer>
|
|
<View style={tw`flex-1 justify-center items-center`}>
|
|
<MyText style={tw`text-gray-600`}>Loading product groups...</MyText>
|
|
</View>
|
|
</AppContainer>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<AppContainer>
|
|
<View style={tw`flex-1 justify-center items-center`}>
|
|
<MyText style={tw`text-red-600`}>Error loading groups</MyText>
|
|
</View>
|
|
</AppContainer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={tw`flex-1 relative px-4 bg-white`}>
|
|
<ScrollView
|
|
style={[tw`flex-1`]}
|
|
contentContainerStyle={tw`pb-32`}
|
|
showsVerticalScrollIndicator={false}
|
|
bounces={false}
|
|
>
|
|
{groups.length === 0 ? (
|
|
<View style={tw`flex-1 justify-center items-center py-8`}>
|
|
<View
|
|
style={tw`w-24 h-24 bg-slate-50 rounded-full items-center justify-center mb-6`}
|
|
>
|
|
<MaterialIcons name="group-work" size={48} color="#94A3B8" />
|
|
</View>
|
|
<MyText
|
|
style={tw`text-slate-900 text-xl font-black tracking-tight`}
|
|
>
|
|
No Groups Yet
|
|
</MyText>
|
|
<MyText
|
|
style={tw`text-slate-500 text-center mt-2 font-medium px-8`}
|
|
>
|
|
Start by creating your first product group using the button below.
|
|
</MyText>
|
|
</View>
|
|
) : (
|
|
groups.map((group, index) => (
|
|
<React.Fragment key={group.id}>
|
|
<GroupItem
|
|
group={group}
|
|
index={index}
|
|
onEdit={handleEdit}
|
|
onDelete={handleDelete}
|
|
onViewProducts={setViewProducts}
|
|
/>
|
|
{index < groups.length - 1 && (
|
|
<View style={tw`h-px bg-slate-200 w-full`} />
|
|
)}
|
|
</React.Fragment>
|
|
))
|
|
)}
|
|
|
|
{/* Global Floating Action Button */}
|
|
</ScrollView>
|
|
<MyTouchableOpacity
|
|
onPress={handleCreate}
|
|
activeOpacity={0.95}
|
|
style={{ position: "absolute", bottom: 32, right: 24, zIndex: 100 }}
|
|
>
|
|
<LinearGradient
|
|
colors={["#1570EF", "#194185"]}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={tw`w-16 h-16 rounded-[24px] items-center justify-center shadow-lg shadow-brand300`}
|
|
>
|
|
<MaterialIcons name="add" size={32} color="white" />
|
|
</LinearGradient>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|