ソースを参照

feat: create Manage Store Data UI page #45

- Created ManageStoreDataContent component with:
  - Three tabs for Products, Orders, Customers
  - Search and filter functionality
  - Data table with toggle switches for each item
  - Bulk action buttons (Enable/Disable Selected, Enable/Disable All)
  - Pagination controls (25/50/100 items per page)
  - Confirmation dialogs for bulk actions
  - Loading and empty states
- Created ManageStoreData page wrapper with sidebar
- Added /manage-store-data protected route to App.tsx
- Updated AI Config page button to navigate to new route with shop parameter
- Follows existing design patterns and uses shadcn-ui components
- Responsive design with dark theme matching existing pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude 5 ヶ月 前
コミット
165e0dc834

+ 2 - 0
shopcall.ai-main/src/App.tsx

@@ -14,6 +14,7 @@ import Analytics from "./pages/Analytics";
 import Webshops from "./pages/Webshops";
 import PhoneNumbers from "./pages/PhoneNumbers";
 import AIConfig from "./pages/AIConfig";
+import ManageStoreData from "./pages/ManageStoreData";
 import Onboarding from "./pages/Onboarding";
 import About from "./pages/About";
 import Privacy from "./pages/Privacy";
@@ -45,6 +46,7 @@ const App = () => (
               <Route path="/webshops" element={<Webshops />} />
               <Route path="/phone-numbers" element={<PhoneNumbers />} />
               <Route path="/ai-config" element={<AIConfig />} />
+              <Route path="/manage-store-data" element={<ManageStoreData />} />
               <Route path="/onboarding" element={<Onboarding />} />
             </Route>
             <Route path="/about" element={<About />} />

+ 1 - 1
shopcall.ai-main/src/components/AIConfigContent.tsx

@@ -452,7 +452,7 @@ export function AIConfigContent() {
             <div className="flex gap-3">
               <Button
                 className="bg-cyan-500 hover:bg-cyan-600 text-white"
-                onClick={() => window.location.href = '/webshops'}
+                onClick={() => window.location.href = `/manage-store-data?shop=${selectedStore.id}`}
               >
                 Manage Store Data
               </Button>

+ 855 - 0
shopcall.ai-main/src/components/ManageStoreDataContent.tsx

@@ -0,0 +1,855 @@
+import { useState, useEffect } from "react";
+import { useSearchParams } from "react-router-dom";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+import { Badge } from "@/components/ui/badge";
+import {
+  AlertDialog,
+  AlertDialogAction,
+  AlertDialogCancel,
+  AlertDialogContent,
+  AlertDialogDescription,
+  AlertDialogFooter,
+  AlertDialogHeader,
+  AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+import { Loader2, Search, Package, ShoppingCart, Users, ChevronLeft, ChevronRight } from "lucide-react";
+import { API_URL } from "@/lib/config";
+import { useToast } from "@/hooks/use-toast";
+
+interface StoreData {
+  id: string;
+  store_name: string | null;
+  platform_name: string;
+  store_url: string | null;
+}
+
+interface Product {
+  id: string;
+  name: string;
+  sku: string;
+  price: string;
+  currency: string;
+  enabled_in_context: boolean;
+}
+
+interface Order {
+  id: string;
+  order_number: string;
+  customer_name: string;
+  total: string;
+  currency: string;
+  enabled_in_context: boolean;
+}
+
+interface Customer {
+  id: string;
+  name: string;
+  email: string;
+  orders_count: number;
+  enabled_in_context: boolean;
+}
+
+type DataItem = Product | Order | Customer;
+
+interface DataResponse {
+  success: boolean;
+  data: DataItem[];
+  total: number;
+  enabled_count: number;
+  disabled_count: number;
+}
+
+export function ManageStoreDataContent() {
+  const [searchParams] = useSearchParams();
+  const { toast } = useToast();
+
+  const [stores, setStores] = useState<StoreData[]>([]);
+  const [selectedStore, setSelectedStore] = useState<StoreData | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [activeTab, setActiveTab] = useState<"products" | "orders" | "customers">("products");
+
+  // Filter and search state
+  const [searchQuery, setSearchQuery] = useState("");
+  const [statusFilter, setStatusFilter] = useState<"all" | "enabled" | "disabled">("all");
+  const [platformFilter, setPlatformFilter] = useState<string>("all");
+
+  // Data state
+  const [data, setData] = useState<DataItem[]>([]);
+  const [dataLoading, setDataLoading] = useState(false);
+  const [totalCount, setTotalCount] = useState(0);
+  const [enabledCount, setEnabledCount] = useState(0);
+  const [disabledCount, setDisabledCount] = useState(0);
+
+  // Pagination state
+  const [page, setPage] = useState(1);
+  const [pageSize, setPageSize] = useState(25);
+
+  // Selection state
+  const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
+  const [selectAll, setSelectAll] = useState(false);
+
+  // Confirmation dialog state
+  const [confirmDialog, setConfirmDialog] = useState<{
+    open: boolean;
+    title: string;
+    description: string;
+    action: () => void;
+  }>({
+    open: false,
+    title: "",
+    description: "",
+    action: () => {},
+  });
+
+  // Fetch stores on mount
+  useEffect(() => {
+    const fetchStores = async () => {
+      try {
+        const sessionData = localStorage.getItem('session_data');
+        if (!sessionData) {
+          throw new Error('No session data found');
+        }
+
+        const session = JSON.parse(sessionData);
+        const response = await fetch(`${API_URL}/api/stores`, {
+          headers: {
+            'Authorization': `Bearer ${session.session.access_token}`,
+            'Content-Type': 'application/json'
+          }
+        });
+
+        if (!response.ok) {
+          throw new Error('Failed to fetch stores');
+        }
+
+        const result = await response.json();
+        if (result.success) {
+          const storesList = result.stores || [];
+          setStores(storesList);
+
+          // Select store from URL parameter or first store
+          const shopId = searchParams.get('shop');
+          if (shopId) {
+            const store = storesList.find((s: StoreData) => s.id === shopId);
+            if (store) {
+              setSelectedStore(store);
+            } else if (storesList.length > 0) {
+              setSelectedStore(storesList[0]);
+            }
+          } else if (storesList.length > 0) {
+            setSelectedStore(storesList[0]);
+          }
+        }
+      } catch (error) {
+        console.error('Error fetching stores:', error);
+        toast({
+          title: "Error",
+          description: "Failed to load stores. Please try again.",
+          variant: "destructive"
+        });
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchStores();
+  }, [searchParams, toast]);
+
+  // Fetch data when tab, store, or filters change
+  useEffect(() => {
+    if (selectedStore) {
+      fetchData();
+    }
+  }, [selectedStore, activeTab, page, pageSize, searchQuery, statusFilter]);
+
+  const fetchData = async () => {
+    if (!selectedStore) return;
+
+    setDataLoading(true);
+    try {
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const session = JSON.parse(sessionData);
+
+      // Build query parameters
+      const params = new URLSearchParams({
+        store_id: selectedStore.id,
+        page: page.toString(),
+        limit: pageSize.toString(),
+      });
+
+      if (searchQuery) {
+        params.append('search', searchQuery);
+      }
+
+      if (statusFilter !== 'all') {
+        params.append('enabled', statusFilter === 'enabled' ? 'true' : 'false');
+      }
+
+      const endpoint = `/api/store-data/${activeTab}?${params.toString()}`;
+      const response = await fetch(`${API_URL}${endpoint}`, {
+        headers: {
+          'Authorization': `Bearer ${session.session.access_token}`,
+          'Content-Type': 'application/json'
+        }
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to fetch data');
+      }
+
+      const result: DataResponse = await response.json();
+      if (result.success) {
+        setData(result.data);
+        setTotalCount(result.total);
+        setEnabledCount(result.enabled_count);
+        setDisabledCount(result.disabled_count);
+      }
+    } catch (error) {
+      console.error('Error fetching data:', error);
+      toast({
+        title: "Error",
+        description: `Failed to load ${activeTab}. Please try again.`,
+        variant: "destructive"
+      });
+    } finally {
+      setDataLoading(false);
+    }
+  };
+
+  const handleToggleItem = async (itemId: string, currentEnabled: boolean) => {
+    if (!selectedStore) return;
+
+    try {
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const session = JSON.parse(sessionData);
+
+      // Optimistic update
+      setData(prevData =>
+        prevData.map(item =>
+          item.id === itemId
+            ? { ...item, enabled_in_context: !currentEnabled }
+            : item
+        )
+      );
+
+      const response = await fetch(`${API_URL}/api/store-data/${activeTab}/${itemId}/toggle`, {
+        method: 'PUT',
+        headers: {
+          'Authorization': `Bearer ${session.session.access_token}`,
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          store_id: selectedStore.id,
+          enabled: !currentEnabled
+        })
+      });
+
+      if (!response.ok) {
+        // Revert on error
+        setData(prevData =>
+          prevData.map(item =>
+            item.id === itemId
+              ? { ...item, enabled_in_context: currentEnabled }
+              : item
+          )
+        );
+        throw new Error('Failed to update item');
+      }
+
+      // Update counts
+      if (currentEnabled) {
+        setEnabledCount(prev => prev - 1);
+        setDisabledCount(prev => prev + 1);
+      } else {
+        setEnabledCount(prev => prev + 1);
+        setDisabledCount(prev => prev - 1);
+      }
+
+      toast({
+        title: "Success",
+        description: `Item ${!currentEnabled ? 'enabled' : 'disabled'} successfully.`,
+      });
+    } catch (error) {
+      console.error('Error toggling item:', error);
+      toast({
+        title: "Error",
+        description: "Failed to update item. Please try again.",
+        variant: "destructive"
+      });
+    }
+  };
+
+  const handleBulkAction = async (enable: boolean) => {
+    if (!selectedStore || selectedItems.size === 0) return;
+
+    try {
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const session = JSON.parse(sessionData);
+
+      const response = await fetch(`${API_URL}/api/store-data/${activeTab}/bulk-toggle`, {
+        method: 'PUT',
+        headers: {
+          'Authorization': `Bearer ${session.session.access_token}`,
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          store_id: selectedStore.id,
+          item_ids: Array.from(selectedItems),
+          enabled: enable
+        })
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to update items');
+      }
+
+      toast({
+        title: "Success",
+        description: `${selectedItems.size} items ${enable ? 'enabled' : 'disabled'} successfully.`,
+      });
+
+      setSelectedItems(new Set());
+      setSelectAll(false);
+      fetchData();
+    } catch (error) {
+      console.error('Error with bulk action:', error);
+      toast({
+        title: "Error",
+        description: "Failed to update items. Please try again.",
+        variant: "destructive"
+      });
+    }
+  };
+
+  const handleEnableAll = async () => {
+    if (!selectedStore) return;
+
+    try {
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const session = JSON.parse(sessionData);
+
+      const response = await fetch(`${API_URL}/api/store-data/${activeTab}/enable-all`, {
+        method: 'PUT',
+        headers: {
+          'Authorization': `Bearer ${session.session.access_token}`,
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          store_id: selectedStore.id
+        })
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to enable all items');
+      }
+
+      toast({
+        title: "Success",
+        description: `All ${activeTab} enabled successfully.`,
+      });
+
+      fetchData();
+    } catch (error) {
+      console.error('Error enabling all:', error);
+      toast({
+        title: "Error",
+        description: "Failed to enable all items. Please try again.",
+        variant: "destructive"
+      });
+    }
+  };
+
+  const handleDisableAll = async () => {
+    if (!selectedStore) return;
+
+    try {
+      const sessionData = localStorage.getItem('session_data');
+      if (!sessionData) {
+        throw new Error('No session data found');
+      }
+
+      const session = JSON.parse(sessionData);
+
+      const response = await fetch(`${API_URL}/api/store-data/${activeTab}/disable-all`, {
+        method: 'PUT',
+        headers: {
+          'Authorization': `Bearer ${session.session.access_token}`,
+          'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+          store_id: selectedStore.id
+        })
+      });
+
+      if (!response.ok) {
+        throw new Error('Failed to disable all items');
+      }
+
+      toast({
+        title: "Success",
+        description: `All ${activeTab} disabled successfully.`,
+      });
+
+      fetchData();
+    } catch (error) {
+      console.error('Error disabling all:', error);
+      toast({
+        title: "Error",
+        description: "Failed to disable all items. Please try again.",
+        variant: "destructive"
+      });
+    }
+  };
+
+  const handleSelectAll = (checked: boolean) => {
+    setSelectAll(checked);
+    if (checked) {
+      setSelectedItems(new Set(data.map(item => item.id)));
+    } else {
+      setSelectedItems(new Set());
+    }
+  };
+
+  const handleSelectItem = (itemId: string, checked: boolean) => {
+    const newSelected = new Set(selectedItems);
+    if (checked) {
+      newSelected.add(itemId);
+    } else {
+      newSelected.delete(itemId);
+    }
+    setSelectedItems(newSelected);
+    setSelectAll(newSelected.size === data.length);
+  };
+
+  const renderProductRow = (product: Product) => (
+    <TableRow key={product.id}>
+      <TableCell>
+        <Checkbox
+          checked={selectedItems.has(product.id)}
+          onCheckedChange={(checked) => handleSelectItem(product.id, checked as boolean)}
+        />
+      </TableCell>
+      <TableCell className="text-white font-medium">{product.name}</TableCell>
+      <TableCell className="text-slate-400">{product.sku || 'N/A'}</TableCell>
+      <TableCell className="text-slate-300">
+        {product.price} {product.currency}
+      </TableCell>
+      <TableCell>
+        <Switch
+          checked={product.enabled_in_context}
+          onCheckedChange={() => handleToggleItem(product.id, product.enabled_in_context)}
+          className="data-[state=checked]:bg-cyan-500"
+        />
+      </TableCell>
+    </TableRow>
+  );
+
+  const renderOrderRow = (order: Order) => (
+    <TableRow key={order.id}>
+      <TableCell>
+        <Checkbox
+          checked={selectedItems.has(order.id)}
+          onCheckedChange={(checked) => handleSelectItem(order.id, checked as boolean)}
+        />
+      </TableCell>
+      <TableCell className="text-white font-medium">{order.order_number}</TableCell>
+      <TableCell className="text-slate-400">{order.customer_name}</TableCell>
+      <TableCell className="text-slate-300">
+        {order.total} {order.currency}
+      </TableCell>
+      <TableCell>
+        <Switch
+          checked={order.enabled_in_context}
+          onCheckedChange={() => handleToggleItem(order.id, order.enabled_in_context)}
+          className="data-[state=checked]:bg-cyan-500"
+        />
+      </TableCell>
+    </TableRow>
+  );
+
+  const renderCustomerRow = (customer: Customer) => (
+    <TableRow key={customer.id}>
+      <TableCell>
+        <Checkbox
+          checked={selectedItems.has(customer.id)}
+          onCheckedChange={(checked) => handleSelectItem(customer.id, checked as boolean)}
+        />
+      </TableCell>
+      <TableCell className="text-white font-medium">{customer.name}</TableCell>
+      <TableCell className="text-slate-400">{customer.email}</TableCell>
+      <TableCell className="text-slate-300">{customer.orders_count}</TableCell>
+      <TableCell>
+        <Switch
+          checked={customer.enabled_in_context}
+          onCheckedChange={() => handleToggleItem(customer.id, customer.enabled_in_context)}
+          className="data-[state=checked]:bg-cyan-500"
+        />
+      </TableCell>
+    </TableRow>
+  );
+
+  if (loading) {
+    return (
+      <div className="flex-1 flex items-center justify-center min-h-screen bg-slate-900">
+        <Loader2 className="w-8 h-8 text-cyan-500 animate-spin" />
+      </div>
+    );
+  }
+
+  if (stores.length === 0) {
+    return (
+      <div className="flex-1 space-y-6 p-8 bg-slate-900">
+        <div className="flex items-center justify-center min-h-[60vh]">
+          <div className="text-center">
+            <Package className="w-16 h-16 text-slate-600 mx-auto mb-4" />
+            <h3 className="text-lg font-semibold text-white mb-2">No Stores Connected</h3>
+            <p className="text-slate-400 mb-6">
+              Please connect a store first before managing data.
+            </p>
+            <Button
+              className="bg-cyan-500 hover:bg-cyan-600 text-white"
+              onClick={() => window.location.href = '/webshops'}
+            >
+              Go to Webshops
+            </Button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  const totalPages = Math.ceil(totalCount / pageSize);
+
+  return (
+    <div className="flex-1 space-y-6 p-8 bg-slate-900">
+      <div className="flex items-center justify-between">
+        <div>
+          <h2 className="text-3xl font-bold tracking-tight text-white">Manage Store Data</h2>
+          <p className="text-slate-400">Control which data is included in AI context</p>
+        </div>
+        {stores.length > 1 && (
+          <Select value={selectedStore?.id} onValueChange={(value) => {
+            const store = stores.find(s => s.id === value);
+            if (store) setSelectedStore(store);
+          }}>
+            <SelectTrigger className="w-[300px] bg-slate-700 border-slate-600 text-white">
+              <SelectValue />
+            </SelectTrigger>
+            <SelectContent className="bg-slate-700 border-slate-600">
+              {stores.map((store) => (
+                <SelectItem key={store.id} value={store.id}>
+                  <div className="flex items-center gap-2">
+                    <span className="text-white">{store.store_name || 'Unnamed Store'}</span>
+                    <span className="text-slate-400 text-sm capitalize">({store.platform_name})</span>
+                  </div>
+                </SelectItem>
+              ))}
+            </SelectContent>
+          </Select>
+        )}
+      </div>
+
+      <Card className="bg-slate-800 border-slate-700">
+        <CardHeader>
+          <CardTitle className="text-white">Data Management</CardTitle>
+        </CardHeader>
+        <CardContent>
+          <Tabs value={activeTab} onValueChange={(value) => {
+            setActiveTab(value as "products" | "orders" | "customers");
+            setPage(1);
+            setSelectedItems(new Set());
+            setSelectAll(false);
+          }}>
+            <TabsList className="bg-slate-700 mb-6">
+              <TabsTrigger value="products" className="data-[state=active]:bg-slate-600 data-[state=active]:text-white">
+                <Package className="w-4 h-4 mr-2" />
+                Products ({enabledCount}/{totalCount})
+              </TabsTrigger>
+              <TabsTrigger value="orders" className="data-[state=active]:bg-slate-600 data-[state=active]:text-white">
+                <ShoppingCart className="w-4 h-4 mr-2" />
+                Orders ({enabledCount}/{totalCount})
+              </TabsTrigger>
+              <TabsTrigger value="customers" className="data-[state=active]:bg-slate-600 data-[state=active]:text-white">
+                <Users className="w-4 h-4 mr-2" />
+                Customers ({enabledCount}/{totalCount})
+              </TabsTrigger>
+            </TabsList>
+
+            {/* Search and Filter Bar */}
+            <div className="flex gap-4 mb-6">
+              <div className="flex-1 relative">
+                <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-slate-400" />
+                <Input
+                  placeholder={`Search ${activeTab}...`}
+                  value={searchQuery}
+                  onChange={(e) => setSearchQuery(e.target.value)}
+                  className="pl-10 bg-slate-700 border-slate-600 text-white"
+                />
+              </div>
+              <Select value={statusFilter} onValueChange={(value) => setStatusFilter(value as "all" | "enabled" | "disabled")}>
+                <SelectTrigger className="w-[180px] bg-slate-700 border-slate-600 text-white">
+                  <SelectValue />
+                </SelectTrigger>
+                <SelectContent className="bg-slate-700 border-slate-600">
+                  <SelectItem value="all">All Items</SelectItem>
+                  <SelectItem value="enabled">Enabled Only</SelectItem>
+                  <SelectItem value="disabled">Disabled Only</SelectItem>
+                </SelectContent>
+              </Select>
+            </div>
+
+            {/* Bulk Actions */}
+            {selectedItems.size > 0 && (
+              <div className="flex gap-2 mb-4 p-3 bg-slate-700/50 rounded-lg">
+                <span className="text-white mr-4">{selectedItems.size} items selected</span>
+                <Button
+                  size="sm"
+                  variant="outline"
+                  onClick={() => handleBulkAction(true)}
+                  className="border-cyan-500 text-cyan-500 hover:bg-cyan-500 hover:text-white"
+                >
+                  Enable Selected
+                </Button>
+                <Button
+                  size="sm"
+                  variant="outline"
+                  onClick={() => handleBulkAction(false)}
+                  className="border-slate-400 text-slate-400 hover:bg-slate-600"
+                >
+                  Disable Selected
+                </Button>
+              </div>
+            )}
+
+            <div className="flex gap-2 mb-4">
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={() => setConfirmDialog({
+                  open: true,
+                  title: `Enable All ${activeTab}`,
+                  description: `Are you sure you want to enable all ${activeTab} for AI context?`,
+                  action: handleEnableAll
+                })}
+                className="border-cyan-500 text-cyan-500 hover:bg-cyan-500 hover:text-white"
+              >
+                Enable All
+              </Button>
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={() => setConfirmDialog({
+                  open: true,
+                  title: `Disable All ${activeTab}`,
+                  description: `Are you sure you want to disable all ${activeTab} from AI context?`,
+                  action: handleDisableAll
+                })}
+                className="border-slate-400 text-slate-400 hover:bg-slate-600"
+              >
+                Disable All
+              </Button>
+            </div>
+
+            <TabsContent value="products">
+              {dataLoading ? (
+                <div className="flex justify-center py-8">
+                  <Loader2 className="w-6 h-6 text-cyan-500 animate-spin" />
+                </div>
+              ) : data.length === 0 ? (
+                <div className="text-center py-8 text-slate-400">
+                  No products found
+                </div>
+              ) : (
+                <div className="rounded-md border border-slate-700">
+                  <Table>
+                    <TableHeader className="bg-slate-700/50">
+                      <TableRow>
+                        <TableHead className="w-12">
+                          <Checkbox
+                            checked={selectAll}
+                            onCheckedChange={handleSelectAll}
+                          />
+                        </TableHead>
+                        <TableHead className="text-slate-300">Name</TableHead>
+                        <TableHead className="text-slate-300">SKU</TableHead>
+                        <TableHead className="text-slate-300">Price</TableHead>
+                        <TableHead className="text-slate-300">Enabled</TableHead>
+                      </TableRow>
+                    </TableHeader>
+                    <TableBody className="bg-slate-800">
+                      {data.map(item => renderProductRow(item as Product))}
+                    </TableBody>
+                  </Table>
+                </div>
+              )}
+            </TabsContent>
+
+            <TabsContent value="orders">
+              {dataLoading ? (
+                <div className="flex justify-center py-8">
+                  <Loader2 className="w-6 h-6 text-cyan-500 animate-spin" />
+                </div>
+              ) : data.length === 0 ? (
+                <div className="text-center py-8 text-slate-400">
+                  No orders found
+                </div>
+              ) : (
+                <div className="rounded-md border border-slate-700">
+                  <Table>
+                    <TableHeader className="bg-slate-700/50">
+                      <TableRow>
+                        <TableHead className="w-12">
+                          <Checkbox
+                            checked={selectAll}
+                            onCheckedChange={handleSelectAll}
+                          />
+                        </TableHead>
+                        <TableHead className="text-slate-300">Order Number</TableHead>
+                        <TableHead className="text-slate-300">Customer</TableHead>
+                        <TableHead className="text-slate-300">Total</TableHead>
+                        <TableHead className="text-slate-300">Enabled</TableHead>
+                      </TableRow>
+                    </TableHeader>
+                    <TableBody className="bg-slate-800">
+                      {data.map(item => renderOrderRow(item as Order))}
+                    </TableBody>
+                  </Table>
+                </div>
+              )}
+            </TabsContent>
+
+            <TabsContent value="customers">
+              {dataLoading ? (
+                <div className="flex justify-center py-8">
+                  <Loader2 className="w-6 h-6 text-cyan-500 animate-spin" />
+                </div>
+              ) : data.length === 0 ? (
+                <div className="text-center py-8 text-slate-400">
+                  No customers found
+                </div>
+              ) : (
+                <div className="rounded-md border border-slate-700">
+                  <Table>
+                    <TableHeader className="bg-slate-700/50">
+                      <TableRow>
+                        <TableHead className="w-12">
+                          <Checkbox
+                            checked={selectAll}
+                            onCheckedChange={handleSelectAll}
+                          />
+                        </TableHead>
+                        <TableHead className="text-slate-300">Name</TableHead>
+                        <TableHead className="text-slate-300">Email</TableHead>
+                        <TableHead className="text-slate-300">Orders</TableHead>
+                        <TableHead className="text-slate-300">Enabled</TableHead>
+                      </TableRow>
+                    </TableHeader>
+                    <TableBody className="bg-slate-800">
+                      {data.map(item => renderCustomerRow(item as Customer))}
+                    </TableBody>
+                  </Table>
+                </div>
+              )}
+            </TabsContent>
+          </Tabs>
+
+          {/* Pagination */}
+          {totalCount > 0 && (
+            <div className="flex items-center justify-between mt-6">
+              <div className="flex items-center gap-4">
+                <Label className="text-slate-300">Items per page:</Label>
+                <Select value={pageSize.toString()} onValueChange={(value) => {
+                  setPageSize(Number(value));
+                  setPage(1);
+                }}>
+                  <SelectTrigger className="w-[100px] bg-slate-700 border-slate-600 text-white">
+                    <SelectValue />
+                  </SelectTrigger>
+                  <SelectContent className="bg-slate-700 border-slate-600">
+                    <SelectItem value="25">25</SelectItem>
+                    <SelectItem value="50">50</SelectItem>
+                    <SelectItem value="100">100</SelectItem>
+                  </SelectContent>
+                </Select>
+              </div>
+
+              <div className="flex items-center gap-2">
+                <span className="text-slate-400 text-sm">
+                  Page {page} of {totalPages} ({totalCount} total)
+                </span>
+                <Button
+                  size="sm"
+                  variant="outline"
+                  onClick={() => setPage(prev => Math.max(1, prev - 1))}
+                  disabled={page === 1}
+                  className="border-slate-600 text-white"
+                >
+                  <ChevronLeft className="w-4 h-4" />
+                </Button>
+                <Button
+                  size="sm"
+                  variant="outline"
+                  onClick={() => setPage(prev => Math.min(totalPages, prev + 1))}
+                  disabled={page === totalPages}
+                  className="border-slate-600 text-white"
+                >
+                  <ChevronRight className="w-4 h-4" />
+                </Button>
+              </div>
+            </div>
+          )}
+        </CardContent>
+      </Card>
+
+      {/* Confirmation Dialog */}
+      <AlertDialog open={confirmDialog.open} onOpenChange={(open) => setConfirmDialog({ ...confirmDialog, open })}>
+        <AlertDialogContent className="bg-slate-800 border-slate-700">
+          <AlertDialogHeader>
+            <AlertDialogTitle className="text-white">{confirmDialog.title}</AlertDialogTitle>
+            <AlertDialogDescription className="text-slate-400">
+              {confirmDialog.description}
+            </AlertDialogDescription>
+          </AlertDialogHeader>
+          <AlertDialogFooter>
+            <AlertDialogCancel className="bg-slate-700 text-white border-slate-600">
+              Cancel
+            </AlertDialogCancel>
+            <AlertDialogAction
+              onClick={() => {
+                confirmDialog.action();
+                setConfirmDialog({ ...confirmDialog, open: false });
+              }}
+              className="bg-cyan-500 hover:bg-cyan-600 text-white"
+            >
+              Confirm
+            </AlertDialogAction>
+          </AlertDialogFooter>
+        </AlertDialogContent>
+      </AlertDialog>
+    </div>
+  );
+}

+ 18 - 0
shopcall.ai-main/src/pages/ManageStoreData.tsx

@@ -0,0 +1,18 @@
+import { SidebarProvider } from "@/components/ui/sidebar";
+import { AppSidebar } from "@/components/AppSidebar";
+import { ManageStoreDataContent } from "@/components/ManageStoreDataContent";
+
+const ManageStoreData = () => {
+  return (
+    <SidebarProvider>
+      <div className="min-h-screen flex w-full bg-slate-900">
+        <AppSidebar />
+        <main className="flex-1">
+          <ManageStoreDataContent />
+        </main>
+      </div>
+    </SidebarProvider>
+  );
+};
+
+export default ManageStoreData;