1
0
Code Issues Pull Requests Actions Packages Projects Releases Wiki Activity Security Code Quality

update filter for customer features

This commit is contained in:
2025-07-10 09:37:40 +07:00
parent b282a71381
commit ec6f0871b4
10 changed files with 964 additions and 297 deletions

View File

@@ -120,6 +120,9 @@ export async function PUT(
lastNameJp: body.customerInfo.lastNameJp,
firstNameJpKana: body.customerInfo.firstNameJpKana,
lastNameJpKana: body.customerInfo.lastNameJpKana,
gender: body.customerInfo.gender,
martial: body.customerInfo.martial,
status: body.customerInfo.status,
email: body.customerContact.email,
mobile: body.customerContact.mobile,
originAdd1: body.customerContact.originAdd1,

View File

@@ -23,6 +23,9 @@ export async function POST(request: NextRequest) {
lastNameJp: validatedData.customerInfo.lastNameJp,
firstNameJpKana: validatedData.customerInfo.firstNameJpKana,
lastNameJpKana: validatedData.customerInfo.lastNameJpKana,
gender: validatedData.customerInfo.gender,
martial: validatedData.customerInfo.martial,
status: validatedData.customerInfo.status,
email: validatedData.customerContact.email,
mobile: validatedData.customerContact.mobile,
originAdd1: validatedData.customerContact.originAdd1,
@@ -110,6 +113,9 @@ export async function GET(request: NextRequest) {
const page = parseInt(searchParams.get("page") || "1");
const pageSize = parseInt(searchParams.get("pageSize") || "10");
const search = searchParams.get("search") || "";
const gender = searchParams.get("gender") || "";
const martial = searchParams.get("martial") || "";
const status = searchParams.get("status") || "";
const sortBy = searchParams.get("sortBy") || "id";
const sortOrder = searchParams.get("sortOrder") || "asc";
@@ -137,6 +143,27 @@ export async function GET(request: NextRequest) {
);
}
// Apply gender filter
if (gender) {
customersWithDependants = customersWithDependants.filter((customer) =>
customer.gender.toLowerCase() === gender.toLowerCase()
);
}
// Apply martial status filter
if (martial) {
customersWithDependants = customersWithDependants.filter((customer) =>
customer.martial.toLowerCase() === martial.toLowerCase()
);
}
// Apply status filter
if (status) {
customersWithDependants = customersWithDependants.filter((customer) =>
customer.status.toLowerCase() === status.toLowerCase()
);
}
// Apply sorting
customersWithDependants.sort((a, b) => {
let aValue: string | number = a[sortBy as keyof typeof a] as string | number;

View File

@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Plus, Trash2, ArrowLeft, Save, Loader2 } from "lucide-react";
@@ -45,6 +46,9 @@ export default function CustomerEditPage() {
lastNameJp: "",
firstNameJpKana: "",
lastNameJpKana: "",
gender: "",
martial: "",
status: "",
},
customerContact: {
email: "",
@@ -96,14 +100,14 @@ export default function CustomerEditPage() {
form.setValue("customerInfo.lastNameJp", customerData.lastNameJp);
form.setValue("customerInfo.firstNameJpKana", customerData.firstNameJpKana);
form.setValue("customerInfo.lastNameJpKana", customerData.lastNameJpKana);
form.setValue("customerInfo.firstNameJp", customerData.firstNameJp);
form.setValue("customerInfo.lastNameJp", customerData.lastNameJp);
form.setValue("customerInfo.firstNameJpKana", customerData.firstNameJpKana);
form.setValue("customerInfo.lastNameJpKana", customerData.lastNameJpKana);
form.setValue("customerInfo.gender", customerData.gender || "");
form.setValue("customerInfo.martial", customerData.martial || "");
form.setValue("customerInfo.status", customerData.status || "");
form.setValue("customerContact.email", customerData.email);
form.setValue("customerContact.mobile", customerData.mobile);
form.setValue("customerContact.originAdd1", customerData.originAdd1);
form.setValue("customerContact.localAdd1", customerData.localAdd1);
form.setValue("customerContact.localAdd1", customerData.localAdd1);
// Convert dependants to the expected format
const dependants: CustomerDependantForm[] = customerData.dependants.map((dep: {
@@ -292,6 +296,13 @@ export default function CustomerEditPage() {
const isValid = await form.trigger([
"customerInfo.firstNameEn",
"customerInfo.lastNameEn",
"customerInfo.firstNameJp",
"customerInfo.lastNameJp",
"customerInfo.firstNameJpKana",
"customerInfo.lastNameJpKana",
"customerInfo.gender",
"customerInfo.martial",
"customerInfo.status"
]);
if (isValid) {
setActiveTab("contact");
@@ -330,8 +341,22 @@ export default function CustomerEditPage() {
return (
values.customerInfo?.firstNameEn?.trim() !== "" &&
values.customerInfo?.lastNameEn?.trim() !== "" &&
values.customerInfo?.firstNameJp?.trim() !== "" &&
values.customerInfo?.lastNameJp?.trim() !== "" &&
values.customerInfo?.firstNameJpKana?.trim() !== "" &&
values.customerInfo?.lastNameJpKana?.trim() !== "" &&
values.customerInfo?.gender?.trim() !== "" &&
values.customerInfo?.martial?.trim() !== "" &&
values.customerInfo?.status?.trim() !== "" &&
!errors.customerInfo?.firstNameEn &&
!errors.customerInfo?.lastNameEn
!errors.customerInfo?.lastNameEn &&
!errors.customerInfo?.firstNameJp &&
!errors.customerInfo?.lastNameJp &&
!errors.customerInfo?.firstNameJpKana &&
!errors.customerInfo?.lastNameJpKana &&
!errors.customerInfo?.gender &&
!errors.customerInfo?.martial &&
!errors.customerInfo?.status
);
} else if (tabName === "contact") {
return (
@@ -490,6 +515,73 @@ export default function CustomerEditPage() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="customerInfo.gender"
render={({ field }) => (
<FormItem>
<FormLabel>Gender</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select gender" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="male">Male</SelectItem>
<SelectItem value="female">Female</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="customerInfo.martial"
render={({ field }) => (
<FormItem>
<FormLabel>Marital Status</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select marital status" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="single">Single</SelectItem>
<SelectItem value="married">Married</SelectItem>
<SelectItem value="divorced">Divorced</SelectItem>
<SelectItem value="widowed">Widowed</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="customerInfo.status"
render={({ field }) => (
<FormItem>
<FormLabel>Status</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select status" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end">
<Button type="button" onClick={handleNextTab}>

View File

@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Plus, Trash2, ArrowLeft } from "lucide-react";
@@ -39,6 +40,9 @@ export default function CustomerAddPage() {
lastNameJp: "",
firstNameJpKana: "",
lastNameJpKana: "",
gender: "",
martial: "",
status: "",
},
customerContact: {
email: "",
@@ -154,7 +158,10 @@ export default function CustomerAddPage() {
"customerInfo.firstNameJp",
"customerInfo.lastNameJp",
"customerInfo.firstNameJpKana",
"customerInfo.lastNameJpKana"
"customerInfo.lastNameJpKana",
"customerInfo.gender",
"customerInfo.martial",
"customerInfo.status"
]);
if (isValid) {
setActiveTab("contact");
@@ -193,8 +200,22 @@ export default function CustomerAddPage() {
return (
values.customerInfo?.firstNameEn?.trim() !== "" &&
values.customerInfo?.lastNameEn?.trim() !== "" &&
values.customerInfo?.firstNameJp?.trim() !== "" &&
values.customerInfo?.lastNameJp?.trim() !== "" &&
values.customerInfo?.firstNameJpKana?.trim() !== "" &&
values.customerInfo?.lastNameJpKana?.trim() !== "" &&
values.customerInfo?.gender?.trim() !== "" &&
values.customerInfo?.martial?.trim() !== "" &&
values.customerInfo?.status?.trim() !== "" &&
!errors.customerInfo?.firstNameEn &&
!errors.customerInfo?.lastNameEn
!errors.customerInfo?.lastNameEn &&
!errors.customerInfo?.firstNameJp &&
!errors.customerInfo?.lastNameJp &&
!errors.customerInfo?.firstNameJpKana &&
!errors.customerInfo?.lastNameJpKana &&
!errors.customerInfo?.gender &&
!errors.customerInfo?.martial &&
!errors.customerInfo?.status
);
} else if (tabName === "contact") {
return (
@@ -347,6 +368,73 @@ export default function CustomerAddPage() {
</FormItem>
)}
/>
<FormField
control={form.control}
name="customerInfo.gender"
render={({ field }) => (
<FormItem>
<FormLabel>Gender</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select gender" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="male">Male</SelectItem>
<SelectItem value="female">Female</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="customerInfo.martial"
render={({ field }) => (
<FormItem>
<FormLabel>Marital Status</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select marital status" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="single">Single</SelectItem>
<SelectItem value="married">Married</SelectItem>
<SelectItem value="divorced">Divorced</SelectItem>
<SelectItem value="widowed">Widowed</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="customerInfo.status"
render={({ field }) => (
<FormItem>
<FormLabel>Status</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select status" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end">
<Button type="button" onClick={handleNextTab}>

View File

@@ -4,7 +4,10 @@ import { useState, useEffect, useCallback, useMemo } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Plus, Loader2, Users, RefreshCw } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { Plus, Loader2, Users, FileText, Download, FileSpreadsheet, Filter, X } from "lucide-react";
import { toast } from "sonner";
import axios from "axios";
import { ServerDataTable } from "@/components/ui/server-data-table";
@@ -31,11 +34,13 @@ export default function CustomerPage() {
const router = useRouter();
const [customers, setCustomers] = useState<Customer[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false);
const [query, setQuery] = useState({
page: 1,
pageSize: 10,
search: "",
gender: "",
martial: "",
status: "",
sortBy: "id",
sortOrder: "asc" as "asc" | "desc",
});
@@ -47,8 +52,12 @@ export default function CustomerPage() {
hasNextPage: false,
hasPreviousPage: false,
});
// Initialize search and sorting state to match query
const [searchValue, setSearchValue] = useState(query.search);
// Individual filter states
const [nameSearch, setNameSearch] = useState("");
const [genderFilter, setGenderFilter] = useState("all");
const [martialFilter, setMartialFilter] = useState("all");
const [statusFilter, setStatusFilter] = useState("all");
// Initialize sorting state to match query
const [sorting, setSorting] = useState<{ id: string; desc: boolean }[]>(
query.sortBy ? [{ id: query.sortBy, desc: query.sortOrder === "desc" }] : []
);
@@ -60,6 +69,9 @@ export default function CustomerPage() {
page: query.page.toString(),
pageSize: query.pageSize.toString(),
search: query.search,
gender: query.gender,
martial: query.martial,
status: query.status,
sortBy: query.sortBy,
sortOrder: query.sortOrder,
});
@@ -75,7 +87,6 @@ export default function CustomerPage() {
toast.error("Failed to load customers");
} finally {
setIsLoading(false);
setIsRefreshing(false);
}
}, [query]);
@@ -116,18 +127,50 @@ export default function CustomerPage() {
}
}, [fetchCustomers, customers]);
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
await fetchCustomers();
toast.success("Customer list refreshed");
}, [fetchCustomers]);
const handlePaginationChange = useCallback((page: number, pageSize: number) => {
setQuery((prev) => ({ ...prev, page, pageSize }));
}, []);
// Search handler - will be called from ServerDataTable after debounce
// Filter handlers - only update local state, don't trigger search
const handleNameSearchChange = useCallback((value: string) => {
setNameSearch(value);
}, []);
const handleGenderFilterChange = useCallback((value: string) => {
setGenderFilter(value);
}, []);
const handleMartialFilterChange = useCallback((value: string) => {
setMartialFilter(value);
}, []);
const handleStatusFilterChange = useCallback((value: string) => {
setStatusFilter(value);
}, []);
// Apply filters when filter button is clicked
const handleApplyFilters = useCallback(() => {
setQuery((prev) => ({
...prev,
search: nameSearch,
gender: genderFilter === "all" ? "" : genderFilter,
martial: martialFilter === "all" ? "" : martialFilter,
status: statusFilter === "all" ? "" : statusFilter,
page: 1
}));
}, [nameSearch, genderFilter, martialFilter, statusFilter]);
const handleClearFilters = useCallback(() => {
setNameSearch("");
setGenderFilter("all");
setMartialFilter("all");
setStatusFilter("all");
setQuery((prev) => ({ ...prev, search: "", gender: "", martial: "", status: "", page: 1 }));
}, []);
// Legacy search handler for ServerDataTable compatibility
const handleSearchChange = useCallback((search: string) => {
setNameSearch(search);
setQuery((prev) => ({ ...prev, search, page: 1 }));
}, []);
@@ -169,19 +212,47 @@ export default function CustomerPage() {
</div>
<div className="flex items-center space-x-2">
<Button
onClick={handleRefresh}
variant="outline"
size="sm"
disabled={isRefreshing}
className="flex items-center space-x-2"
>
<RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
<span>Refresh</span>
<FileText className="h-4 w-4" />
<span>PDF Export</span>
</Button>
<Button onClick={handleAddCustomer} className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
className="flex items-center space-x-2"
>
<Download className="h-4 w-4" />
<span>CSV Download</span>
</Button>
<Button
variant="outline"
size="sm"
className="flex items-center space-x-2"
>
<FileSpreadsheet className="h-4 w-4" />
<span>Excel Download</span>
</Button>
<Button
onClick={handleAddCustomer}
variant="outline"
size="sm"
className="flex items-center space-x-2"
>
<Plus className="h-4 w-4" />
<span>Add Customer</span>
</Button>
<Button
onClick={handleApplyFilters}
variant="outline"
size="sm"
className="flex items-center space-x-2"
>
<Filter className="h-4 w-4" />
<span>Filter</span>
</Button>
</div>
</div>
@@ -193,20 +264,95 @@ export default function CustomerPage() {
</CardDescription>
</CardHeader>
<CardContent>
{/* Filter Section */}
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Search & Filter</h3>
<Button
variant="outline"
size="sm"
onClick={handleClearFilters}
className="flex items-center space-x-2"
>
<X className="h-4 w-4" />
<span>Clear All</span>
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Name Search */}
<div className="space-y-2">
<Label htmlFor="nameSearch">Search by Name</Label>
<Input
id="nameSearch"
placeholder="Enter customer name..."
value={nameSearch}
onChange={(e) => handleNameSearchChange(e.target.value)}
/>
</div>
{/* Gender Filter */}
<div className="space-y-2">
<Label htmlFor="genderFilter">Gender</Label>
<Select value={genderFilter} onValueChange={handleGenderFilterChange}>
<SelectTrigger id="genderFilter">
<SelectValue placeholder="Select gender" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Genders</SelectItem>
<SelectItem value="male">Male</SelectItem>
<SelectItem value="female">Female</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
{/* Marital Status Filter */}
<div className="space-y-2">
<Label htmlFor="martialFilter">Marital Status</Label>
<Select value={martialFilter} onValueChange={handleMartialFilterChange}>
<SelectTrigger id="martialFilter">
<SelectValue placeholder="Select marital status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="single">Single</SelectItem>
<SelectItem value="married">Married</SelectItem>
<SelectItem value="divorced">Divorced</SelectItem>
<SelectItem value="widowed">Widowed</SelectItem>
</SelectContent>
</Select>
</div>
{/* Status Filter */}
<div className="space-y-2">
<Label htmlFor="statusFilter">Status</Label>
<Select value={statusFilter} onValueChange={handleStatusFilterChange}>
<SelectTrigger id="statusFilter">
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{pagination.total > 0 ? (
<ServerDataTable
columns={columns}
data={customers}
pagination={pagination}
searchKey="name"
searchPlaceholder="Search customers by name..."
isLoading={isLoading}
onPaginationChange={handlePaginationChange}
onSearchChange={handleSearchChange}
onSortingChange={handleSortingChange}
searchValue={searchValue}
searchValue={nameSearch}
sorting={sorting}
setSearchValue={setSearchValue}
setSearchValue={setNameSearch}
setSorting={setSorting}
/>
) : (

View File

@@ -14,6 +14,9 @@ export interface Customer {
mobile: string;
originAdd1: string;
localAdd1: string;
gender: string;
martial: string;
status: string;
dependants?: Array<{
id: number;
custId: number;
@@ -109,6 +112,87 @@ export const createCustomerColumns = ({
return <div className="font-mono">{row.getValue("mobile")}</div>;
},
},
{
accessorKey: "gender",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-8 px-2 lg:px-3"
>
Gender
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const gender = row.getValue("gender") as string;
return (
<Badge variant="outline" className="capitalize">
{gender || "Not specified"}
</Badge>
);
},
},
{
accessorKey: "martial",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-8 px-2 lg:px-3"
>
Marital Status
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const martial = row.getValue("martial") as string;
return (
<Badge variant="outline" className="capitalize">
{martial || "Not specified"}
</Badge>
);
},
},
{
accessorKey: "status",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
className="h-8 px-2 lg:px-3"
>
Status
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const status = row.getValue("status") as string;
const getStatusVariant = (status: string) => {
switch (status?.toLowerCase()) {
case "active":
return "default";
case "inactive":
return "secondary";
case "pending":
return "outline";
default:
return "outline";
}
};
return (
<Badge variant={getStatusVariant(status)} className="capitalize">
{status || "Not specified"}
</Badge>
);
},
},
{
id: "dependants",
header: "Dependants",

View File

@@ -27,8 +27,8 @@ export interface Customer {
// localCountry: string;
// preferLang: string;
// dob: string; // ISO string
// gender: string;
// martial: string;
gender: string;
martial: string;
// occupation: string;
// ecName: string;
// ecRelation: string;
@@ -38,7 +38,7 @@ export interface Customer {
// initialOutReachRemark?: string;
// consentGiven?: string;
// contentDate?: string;
// status: string;
status: string;
// remarks: string;
// potcustId?: string;
// isDeleted: boolean;

View File

@@ -11,7 +11,10 @@
"email": "john.smith@email.com",
"mobile": "+1-555-0101",
"originAdd1": "123 Main Street, New York, NY 10001, USA",
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001",
"gender": "other",
"martial": "married",
"status": "active"
},
{
"id": 2,
@@ -24,7 +27,10 @@
"email": "sarah.johnson@email.com",
"mobile": "+1-555-0102",
"originAdd1": "789 Pine Road, Los Angeles, CA 90210, USA",
"localAdd1": "321 Sakura Street, Osaka, Japan 530-0001"
"localAdd1": "321 Sakura Street, Osaka, Japan 530-0001",
"gender": "female",
"martial": "single",
"status": "active"
},
{
"id": 3,
@@ -37,7 +43,10 @@
"email": "michael.brown@email.com",
"mobile": "+44-20-7946-0958",
"originAdd1": "42 Victoria Street, London, UK SW1H 0TL",
"localAdd1": "789 Shibuya Crossing, Tokyo, Japan 150-0002"
"localAdd1": "789 Shibuya Crossing, Tokyo, Japan 150-0002",
"gender": "male",
"martial": "divorced",
"status": "pending"
},
{
"id": 4,
@@ -50,7 +59,10 @@
"email": "emily.davis@email.com",
"mobile": "+61-2-9876-5432",
"originAdd1": "15 Collins Street, Melbourne, VIC 3000, Australia",
"localAdd1": "654 Namba District, Osaka, Japan 542-0076"
"localAdd1": "654 Namba District, Osaka, Japan 542-0076",
"gender": "female",
"martial": "married",
"status": "active"
},
{
"id": 5,
@@ -63,7 +75,186 @@
"email": "david.wilson@email.com",
"mobile": "+1-416-555-0199",
"originAdd1": "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
"localAdd1": "987 Ginza District, Tokyo, Japan 104-0061"
"localAdd1": "987 Ginza District, Tokyo, Japan 104-0061",
"gender": "male",
"martial": "single",
"status": "inactive"
},
{
"id": 6,
"firstNameEn": "Maria",
"lastNameEn": "Garcia",
"firstNameJp": "マリア",
"lastNameJp": "ガルシア",
"firstNameJpKana": "マリア",
"lastNameJpKana": "ガルシア",
"email": "maria.garcia@email.com",
"mobile": "+34-91-555-0123",
"originAdd1": "Calle Gran Vía 45, Madrid, Spain 28013",
"localAdd1": "246 Harajuku Street, Tokyo, Japan 150-0001",
"gender": "female",
"martial": "married",
"status": "active"
},
{
"id": 7,
"firstNameEn": "James",
"lastNameEn": "Lee",
"firstNameJp": "ジェームス",
"lastNameJp": "リー",
"firstNameJpKana": "ジェームス",
"lastNameJpKana": "リー",
"email": "james.lee@email.com",
"mobile": "+65-6555-0187",
"originAdd1": "1 Raffles Place, Singapore 048616",
"localAdd1": "135 Roppongi Hills, Tokyo, Japan 106-6108",
"gender": "male",
"martial": "single",
"status": "active"
},
{
"id": 8,
"firstNameEn": "Anna",
"lastNameEn": "Mueller",
"firstNameJp": "アンナ",
"lastNameJp": "ミュラー",
"firstNameJpKana": "アンナ",
"lastNameJpKana": "ミュラー",
"email": "anna.mueller@email.com",
"mobile": "+49-30-555-0234",
"originAdd1": "Unter den Linden 77, Berlin, Germany 10117",
"localAdd1": "567 Shinjuku District, Tokyo, Japan 160-0022",
"gender": "female",
"martial": "widowed",
"status": "pending"
},
{
"id": 9,
"firstNameEn": "Robert",
"lastNameEn": "Taylor",
"firstNameJp": "ロバート",
"lastNameJp": "テイラー",
"firstNameJpKana": "ロバート",
"lastNameJpKana": "テイラー",
"email": "robert.taylor@email.com",
"mobile": "+1-555-0145",
"originAdd1": "890 Broadway, New York, NY 10003, USA",
"localAdd1": "890 Akasaka District, Tokyo, Japan 107-0052",
"gender": "male",
"martial": "married",
"status": "inactive"
},
{
"id": 10,
"firstNameEn": "Lisa",
"lastNameEn": "Anderson",
"firstNameJp": "リサ",
"lastNameJp": "アンダーソン",
"firstNameJpKana": "リサ",
"lastNameJpKana": "アンダーソン",
"email": "lisa.anderson@email.com",
"mobile": "+1-555-0167",
"originAdd1": "456 Sunset Boulevard, Los Angeles, CA 90028, USA",
"localAdd1": "234 Ikebukuro District, Tokyo, Japan 171-0021",
"gender": "female",
"martial": "divorced",
"status": "active"
},
{
"id": 11,
"firstNameEn": "Alex",
"lastNameEn": "Chen",
"firstNameJp": "アレックス",
"lastNameJp": "チェン",
"firstNameJpKana": "アレックス",
"lastNameJpKana": "チェン",
"email": "alex.chen@email.com",
"mobile": "+86-21-555-0189",
"originAdd1": "88 Nanjing Road, Shanghai, China 200001",
"localAdd1": "678 Aoyama District, Tokyo, Japan 107-0061",
"gender": "other",
"martial": "single",
"status": "active"
},
{
"id": 12,
"firstNameEn": "Sophie",
"lastNameEn": "Martin",
"firstNameJp": "ソフィー",
"lastNameJp": "マルタン",
"firstNameJpKana": "ソフィー",
"lastNameJpKana": "マルタン",
"email": "sophie.martin@email.com",
"mobile": "+33-1-555-0212",
"originAdd1": "25 Champs-Élysées, Paris, France 75008",
"localAdd1": "345 Ebisu District, Tokyo, Japan 150-0013",
"gender": "female",
"martial": "married",
"status": "pending"
},
{
"id": 13,
"firstNameEn": "Thomas",
"lastNameEn": "White",
"firstNameJp": "トーマス",
"lastNameJp": "ホワイト",
"firstNameJpKana": "トーマス",
"lastNameJpKana": "ホワイト",
"email": "thomas.white@email.com",
"mobile": "+1-555-0234",
"originAdd1": "789 Michigan Avenue, Chicago, IL 60611, USA",
"localAdd1": "456 Kanda District, Tokyo, Japan 101-0047",
"gender": "male",
"martial": "single",
"status": "active"
},
{
"id": 14,
"firstNameEn": "Isabella",
"lastNameEn": "Rodriguez",
"firstNameJp": "イザベラ",
"lastNameJp": "ロドリゲス",
"firstNameJpKana": "イザベラ",
"lastNameJpKana": "ロドリゲス",
"email": "isabella.rodriguez@email.com",
"mobile": "+52-55-555-0256",
"originAdd1": "Avenida Reforma 123, Mexico City, Mexico 06600",
"localAdd1": "789 Ueno District, Tokyo, Japan 110-0005",
"gender": "female",
"martial": "married",
"status": "inactive"
},
{
"id": 15,
"firstNameEn": "William",
"lastNameEn": "Thompson",
"firstNameJp": "ウィリアム",
"lastNameJp": "トンプソン",
"firstNameJpKana": "ウィリアム",
"lastNameJpKana": "トンプソン",
"email": "william.thompson@email.com",
"mobile": "+1-555-0278",
"originAdd1": "567 Market Street, San Francisco, CA 94105, USA",
"localAdd1": "123 Sumida District, Tokyo, Japan 130-0013",
"gender": "male",
"martial": "divorced",
"status": "active"
},
{
"id": 16,
"firstNameEn": "Do",
"lastNameEn": "Tung",
"firstNameJp": "ádf",
"lastNameJp": "ádf",
"firstNameJpKana": "ádf",
"lastNameJpKana": "ádf",
"gender": "female",
"martial": "married",
"status": "active",
"email": "dothanhtung196@gmail.com",
"mobile": "0987417491",
"originAdd1": "1231231231",
"localAdd1": "asdfsdaf"
}
],
"users": [
@@ -210,260 +401,160 @@
},
{
"id": 9,
"userId": 1,
"permissionId": 9
},
{
"id": 10,
"userId": 1,
"permissionId": 10
},
{
"id": 11,
"userId": 1,
"permissionId": 11
},
{
"id": 12,
"userId": 1,
"permissionId": 12
},
{
"id": 13,
"userId": 1,
"permissionId": 13
},
{
"id": 14,
"userId": 1,
"permissionId": 14
},
{
"id": 15,
"userId": 1,
"permissionId": 15
},
{
"id": 16,
"userId": 2,
"permissionId": 4
},
{
"id": 17,
"userId": 2,
"permissionId": 5
},
{
"id": 18,
"id": 10,
"userId": 2,
"permissionId": 6
},
{
"id": 11,
"userId": 2,
"permissionId": 8
},
{
"id": 19,
"userId": 2,
"permissionId": 11
},
{
"id": 20,
"id": 12,
"userId": 3,
"permissionId": 1
},
{
"id": 21,
"id": 13,
"userId": 3,
"permissionId": 2
},
{
"id": 22,
"id": 14,
"userId": 3,
"permissionId": 14
"permissionId": 4
},
{
"id": 23,
"userId": 3,
"id": 15,
"userId": 4,
"permissionId": 5
},
{
"id": 16,
"userId": 4,
"permissionId": 6
},
{
"id": 17,
"userId": 4,
"permissionId": 7
},
{
"id": 18,
"userId": 4,
"permissionId": 1
},
{
"id": 19,
"userId": 5,
"permissionId": 5
},
{
"id": 20,
"userId": 5,
"permissionId": 6
},
{
"id": 21,
"userId": 5,
"permissionId": 8
},
{
"id": 22,
"userId": 6,
"permissionId": 1
},
{
"id": 23,
"userId": 6,
"permissionId": 2
},
{
"id": 24,
"userId": 4,
"userId": 6,
"permissionId": 4
},
{
"id": 25,
"userId": 4,
"permissionId": 1
"userId": 6,
"permissionId": 8
},
{
"id": 26,
"userId": 4,
"userId": 7,
"permissionId": 8
},
{
"id": 27,
"userId": 4,
"permissionId": 9
"userId": 7,
"permissionId": 5
},
{
"id": 28,
"userId": 4,
"permissionId": 15
"userId": 8,
"permissionId": 5
},
{
"id": 29,
"userId": 5,
"permissionId": 4
"userId": 8,
"permissionId": 1
},
{
"id": 30,
"userId": 5,
"userId": 9,
"permissionId": 5
},
{
"id": 31,
"userId": 5,
"permissionId": 11
},
{
"id": 32,
"userId": 5,
"permissionId": 12
},
{
"id": 33,
"userId": 6,
"permissionId": 7
},
{
"id": 34,
"userId": 6,
"permissionId": 10
},
{
"id": 35,
"userId": 6,
"permissionId": 15
},
{
"id": 36,
"userId": 6,
"permissionId": 1
},
{
"id": 37,
"userId": 6,
"permissionId": 2
},
{
"id": 38,
"userId": 7,
"permissionId": 11
},
{
"id": 39,
"userId": 7,
"permissionId": 12
},
{
"id": 40,
"userId": 7,
"permissionId": 13
},
{
"id": 41,
"userId": 7,
"permissionId": 4
},
{
"id": 42,
"userId": 8,
"permissionId": 4
},
{
"id": 43,
"userId": 8,
"permissionId": 1
},
{
"id": 44,
"userId": 8,
"permissionId": 11
},
{
"id": 45,
"userId": 9,
"permissionId": 4
},
{
"id": 46,
"userId": 9,
"permissionId": 5
},
{
"id": 47,
"userId": 9,
"permissionId": 6
},
{
"id": 48,
"id": 32,
"userId": 9,
"permissionId": 7
},
{
"id": 33,
"userId": 9,
"permissionId": 8
},
{
"id": 49,
"userId": 9,
"permissionId": 11
},
{
"id": 50,
"id": 34,
"userId": 10,
"permissionId": 4
"permissionId": 5
},
{
"id": 51,
"id": 35,
"userId": 10,
"permissionId": 1
},
{
"id": 52,
"userId": 10,
"permissionId": 8
},
{
"id": 53,
"userId": 10,
"permissionId": 9
},
{
"id": 54,
"userId": 11,
"permissionId": 4
},
{
"id": 55,
"id": 36,
"userId": 11,
"permissionId": 5
},
{
"id": 56,
"id": 37,
"userId": 11,
"permissionId": 6
},
{
"id": 38,
"userId": 11,
"permissionId": 8
},
{
"id": 57,
"userId": 11,
"permissionId": 11
},
{
"id": 58,
"userId": 11,
"permissionId": 12
}
],
"permissions": [
{
"id": 1,
"name": "user_read",
"description": "Read user information",
"description": "View user list and user details",
"isActive": true
},
{
@@ -480,106 +571,36 @@
},
{
"id": 4,
"name": "customer_read",
"description": "Read customer information",
"name": "user_permissions",
"description": "Manage user permissions",
"isActive": true
},
{
"id": 5,
"name": "customer_read",
"description": "View customer list and customer details",
"isActive": true
},
{
"id": 6,
"name": "customer_write",
"description": "Create and update customer information",
"isActive": true
},
{
"id": 6,
"id": 7,
"name": "customer_delete",
"description": "Delete customer records",
"isActive": true
},
{
"id": 7,
"name": "admin_panel",
"description": "Access administrative panel",
"isActive": true
},
{
"id": 8,
"name": "reports_view",
"description": "View system reports",
"isActive": true
},
{
"id": 9,
"name": "reports_export",
"description": "Export reports and data",
"isActive": true
},
{
"id": 10,
"name": "system_settings",
"description": "Modify system configuration",
"isActive": true
},
{
"id": 11,
"name": "mail_template_read",
"description": "Read mail templates",
"isActive": true
},
{
"id": 12,
"name": "mail_template_write",
"description": "Create and update mail templates",
"isActive": true
},
{
"id": 13,
"name": "mail_template_delete",
"description": "Delete mail templates",
"isActive": true
},
{
"id": 14,
"name": "permission_manage",
"description": "Manage user permissions",
"isActive": true
},
{
"id": 15,
"name": "audit_log",
"description": "View audit logs and system activities",
"name": "mail_template_access",
"description": "Access mail template configuration",
"isActive": true
}
],
"customerDependants": [
{
"id": 1,
"custId": 1,
"firstNameEn": "Jane",
"lastNameEn": "Smith",
"firstNameJp": "ジェーン",
"lastNameJp": "スミス",
"firstNameJpKana": "ジェーン",
"lastNameJpKana": "スミス",
"email": "jane.smith@email.com",
"mobile": "+1-555-0111",
"originAdd1": "123 Main Street, New York, NY 10001, USA",
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
},
{
"id": 2,
"custId": 1,
"firstNameEn": "Tommy",
"lastNameEn": "Smith",
"firstNameJp": "トミー",
"lastNameJp": "スミス",
"firstNameJpKana": "トミー",
"lastNameJpKana": "スミス",
"email": "tommy.smith@email.com",
"mobile": "+1-555-0112",
"originAdd1": "123 Main Street, New York, NY 10001, USA",
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
},
{
"id": 3,
"custId": 2,
@@ -677,6 +698,34 @@
"mobile": "+1-416-555-0191",
"originAdd1": "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
"localAdd1": "987 Ginza District, Tokyo, Japan 104-0061"
},
{
"id": 1,
"custId": 1,
"firstNameEn": "Jane",
"lastNameEn": "Smith",
"firstNameJp": "ジェーン",
"lastNameJp": "スミス",
"firstNameJpKana": "ジェーン",
"lastNameJpKana": "スミス",
"email": "jane.smith@email.com",
"mobile": "+1-555-0111",
"originAdd1": "123 Main Street, New York, NY 10001, USA",
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
},
{
"id": 2,
"custId": 1,
"firstNameEn": "Tommy",
"lastNameEn": "Smith",
"firstNameJp": "トミー",
"lastNameJp": "スミス",
"firstNameJpKana": "トミー",
"lastNameJpKana": "スミス",
"email": "tommy.smith@email.com",
"mobile": "+1-555-0112",
"originAdd1": "123 Main Street, New York, NY 10001, USA",
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
}
]
}

View File

@@ -13,6 +13,9 @@ export const sampleCustomers: Customer[] = [
mobile: "+1-555-0101",
originAdd1: "123 Main Street, New York, NY 10001, USA",
localAdd1: "456 Oak Avenue, Tokyo, Japan 150-0001",
gender: "male",
martial: "married",
status: "active",
},
{
id: 2,
@@ -26,6 +29,9 @@ export const sampleCustomers: Customer[] = [
mobile: "+1-555-0102",
originAdd1: "789 Pine Road, Los Angeles, CA 90210, USA",
localAdd1: "321 Sakura Street, Osaka, Japan 530-0001",
gender: "female",
martial: "single",
status: "active",
},
{
id: 3,
@@ -39,6 +45,9 @@ export const sampleCustomers: Customer[] = [
mobile: "+44-20-7946-0958",
originAdd1: "42 Victoria Street, London, UK SW1H 0TL",
localAdd1: "789 Shibuya Crossing, Tokyo, Japan 150-0002",
gender: "male",
martial: "divorced",
status: "pending",
},
{
id: 4,
@@ -52,6 +61,9 @@ export const sampleCustomers: Customer[] = [
mobile: "+61-2-9876-5432",
originAdd1: "15 Collins Street, Melbourne, VIC 3000, Australia",
localAdd1: "654 Namba District, Osaka, Japan 542-0076",
gender: "female",
martial: "married",
status: "active",
},
{
id: 5,
@@ -65,5 +77,168 @@ export const sampleCustomers: Customer[] = [
mobile: "+1-416-555-0199",
originAdd1: "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
localAdd1: "987 Ginza District, Tokyo, Japan 104-0061",
gender: "male",
martial: "single",
status: "inactive",
},
{
id: 6,
firstNameEn: "Maria",
lastNameEn: "Garcia",
firstNameJp: "マリア",
lastNameJp: "ガルシア",
firstNameJpKana: "マリア",
lastNameJpKana: "ガルシア",
email: "maria.garcia@email.com",
mobile: "+34-91-555-0123",
originAdd1: "Calle Gran Vía 45, Madrid, Spain 28013",
localAdd1: "246 Harajuku Street, Tokyo, Japan 150-0001",
gender: "female",
martial: "married",
status: "active",
},
{
id: 7,
firstNameEn: "James",
lastNameEn: "Lee",
firstNameJp: "ジェームス",
lastNameJp: "リー",
firstNameJpKana: "ジェームス",
lastNameJpKana: "リー",
email: "james.lee@email.com",
mobile: "+65-6555-0187",
originAdd1: "1 Raffles Place, Singapore 048616",
localAdd1: "135 Roppongi Hills, Tokyo, Japan 106-6108",
gender: "male",
martial: "single",
status: "active",
},
{
id: 8,
firstNameEn: "Anna",
lastNameEn: "Mueller",
firstNameJp: "アンナ",
lastNameJp: "ミュラー",
firstNameJpKana: "アンナ",
lastNameJpKana: "ミュラー",
email: "anna.mueller@email.com",
mobile: "+49-30-555-0234",
originAdd1: "Unter den Linden 77, Berlin, Germany 10117",
localAdd1: "567 Shinjuku District, Tokyo, Japan 160-0022",
gender: "female",
martial: "widowed",
status: "pending",
},
{
id: 9,
firstNameEn: "Robert",
lastNameEn: "Taylor",
firstNameJp: "ロバート",
lastNameJp: "テイラー",
firstNameJpKana: "ロバート",
lastNameJpKana: "テイラー",
email: "robert.taylor@email.com",
mobile: "+1-555-0145",
originAdd1: "890 Broadway, New York, NY 10003, USA",
localAdd1: "890 Akasaka District, Tokyo, Japan 107-0052",
gender: "male",
martial: "married",
status: "inactive",
},
{
id: 10,
firstNameEn: "Lisa",
lastNameEn: "Anderson",
firstNameJp: "リサ",
lastNameJp: "アンダーソン",
firstNameJpKana: "リサ",
lastNameJpKana: "アンダーソン",
email: "lisa.anderson@email.com",
mobile: "+1-555-0167",
originAdd1: "456 Sunset Boulevard, Los Angeles, CA 90028, USA",
localAdd1: "234 Ikebukuro District, Tokyo, Japan 171-0021",
gender: "female",
martial: "divorced",
status: "active",
},
{
id: 11,
firstNameEn: "Alex",
lastNameEn: "Chen",
firstNameJp: "アレックス",
lastNameJp: "チェン",
firstNameJpKana: "アレックス",
lastNameJpKana: "チェン",
email: "alex.chen@email.com",
mobile: "+86-21-555-0189",
originAdd1: "88 Nanjing Road, Shanghai, China 200001",
localAdd1: "678 Aoyama District, Tokyo, Japan 107-0061",
gender: "other",
martial: "single",
status: "active",
},
{
id: 12,
firstNameEn: "Sophie",
lastNameEn: "Martin",
firstNameJp: "ソフィー",
lastNameJp: "マルタン",
firstNameJpKana: "ソフィー",
lastNameJpKana: "マルタン",
email: "sophie.martin@email.com",
mobile: "+33-1-555-0212",
originAdd1: "25 Champs-Élysées, Paris, France 75008",
localAdd1: "345 Ebisu District, Tokyo, Japan 150-0013",
gender: "female",
martial: "married",
status: "pending",
},
{
id: 13,
firstNameEn: "Thomas",
lastNameEn: "White",
firstNameJp: "トーマス",
lastNameJp: "ホワイト",
firstNameJpKana: "トーマス",
lastNameJpKana: "ホワイト",
email: "thomas.white@email.com",
mobile: "+1-555-0234",
originAdd1: "789 Michigan Avenue, Chicago, IL 60611, USA",
localAdd1: "456 Kanda District, Tokyo, Japan 101-0047",
gender: "male",
martial: "single",
status: "active",
},
{
id: 14,
firstNameEn: "Isabella",
lastNameEn: "Rodriguez",
firstNameJp: "イザベラ",
lastNameJp: "ロドリゲス",
firstNameJpKana: "イザベラ",
lastNameJpKana: "ロドリゲス",
email: "isabella.rodriguez@email.com",
mobile: "+52-55-555-0256",
originAdd1: "Avenida Reforma 123, Mexico City, Mexico 06600",
localAdd1: "789 Ueno District, Tokyo, Japan 110-0005",
gender: "female",
martial: "married",
status: "inactive",
},
{
id: 15,
firstNameEn: "William",
lastNameEn: "Thompson",
firstNameJp: "ウィリアム",
lastNameJp: "トンプソン",
firstNameJpKana: "ウィリアム",
lastNameJpKana: "トンプソン",
email: "william.thompson@email.com",
mobile: "+1-555-0278",
originAdd1: "567 Market Street, San Francisco, CA 94105, USA",
localAdd1: "123 Sumida District, Tokyo, Japan 130-0013",
gender: "male",
martial: "divorced",
status: "active",
},
];

View File

@@ -7,6 +7,9 @@ export const customerInfoSchema = z.object({
lastNameJp: z.string().min(1, "Last name in Japanese is required"),
firstNameJpKana: z.string().min(1, "First name in Japanese Kana is required"),
lastNameJpKana: z.string().min(1, "Last name in Japanese Kana is required"),
gender: z.string().min(1, "Gender is required"),
martial: z.string().min(1, "Marital status is required"),
status: z.string().min(1, "Status is required"),
});
export const customerContactSchema = z.object({