app: people and company @frontend added no test runned

This commit is contained in:
Berkay 2025-06-25 11:56:42 +03:00
parent c259ad3d99
commit 5c640ddcee
32 changed files with 2794 additions and 81 deletions

View File

@ -71,7 +71,6 @@ def super_people_list_callable(list_options: PaginateOnly, token: TokenDictType)
people_list = People.query.filter(*People.convert(list_options.query)) people_list = People.query.filter(*People.convert(list_options.query))
else: else:
people_list = People.query.filter() people_list = People.query.filter()
pagination = Pagination(data=people_list) pagination = Pagination(data=people_list)
pagination.change(**list_options.model_dump()) pagination.change(**list_options.model_dump())
pagination_result = PaginationResult(data=people_list, pagination=pagination) pagination_result = PaginationResult(data=people_list, pagination=pagination)

View File

@ -11,11 +11,8 @@ const getMenuFromRedis = async (): Promise<ClientMenu> => {
const decrpytUserSelection = await functionRetrieveUserSelection() const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey; const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new AuthError("No redis key found"); if (!redisKey) throw new AuthError("No redis key found");
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) throw new AuthError("No data found in redis"); if (!result) throw new AuthError("No data found in redis");
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { menu: defaultValuesMenu }); const parsedResult = safeJsonParse(result, { menu: defaultValuesMenu });
if (!parsedResult.menu) throw new AuthError("No menu found in redis"); if (!parsedResult.menu) throw new AuthError("No menu found in redis");
return parsedResult.menu; return parsedResult.menu;
@ -31,12 +28,7 @@ const setMenuToRedis = async (menuObject: string[]) => {
if (!menuObject) throw new AuthError("No menu object provided"); if (!menuObject) throw new AuthError("No menu object provided");
const oldData = await getCompleteFromRedis(); const oldData = await getCompleteFromRedis();
if (!oldData) throw new AuthError("No old data found in redis"); if (!oldData) throw new AuthError("No old data found in redis");
await setCompleteToRedis({ await setCompleteToRedis({ ...oldData, menu: { ...oldData.menu, selectionList: menuObject } });
...oldData, menu: {
...oldData.menu,
selectionList: menuObject,
}
});
return true; return true;
} catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } } } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } }
} }

View File

@ -24,14 +24,14 @@ const menuTranslationEn = {
{ value: "Dashboard", key: "dashboard" }, { value: "Dashboard", key: "dashboard" },
{ value: "Dashboard", key: "dashboard" }, { value: "Dashboard", key: "dashboard" },
], ],
"/individual": [ "/people": [
{ value: "Individual", key: "individual" },
{ value: "Individual", key: "individual" }, { value: "Individual", key: "individual" },
{ value: "Individual", key: "individual" }, { value: "Individual", key: "individual" },
{ value: "People", key: "people" },
], ],
"/user": [ "/user": [
{ value: "User", key: "user" }, { value: "Individual", key: "individual" },
{ value: "User", key: "user" }, { value: "Individual", key: "individual" },
{ value: "User", key: "user" }, { value: "User", key: "user" },
], ],
"/build": [ "/build": [
@ -42,7 +42,7 @@ const menuTranslationEn = {
"/build/parts": [ "/build/parts": [
{ value: "Build", key: "build" }, { value: "Build", key: "build" },
{ value: "Parts", key: "parts" }, { value: "Parts", key: "parts" },
{ value: "Build", key: "build" }, { value: "Build Parts", key: "build" },
], ],
"/management/budget/actions": [ "/management/budget/actions": [
{ value: "Management", key: "management" }, { value: "Management", key: "management" },

View File

@ -1,22 +1,3 @@
// const menuTranslationTr = {
// "/definitions/identifications/people": "Kişiler",
// "/definitions/identifications/users": "Kullanıcılar",
// "/definitions/building/parts": "Daireler",
// "/definitions/building/areas": "Bina Alanları",
// "/building/accounts/managment/accounts": "Bina Hesapları",
// "/building/accounts/managment/budgets": "Bina Bütçesi",
// "/building/accounts/parts/accounts": "Daire Hesapları",
// "/building/accounts/parts/budgets": "Daire Bütçesi",
// "/building/meetings/regular/actions": "Düzenli Toplantı Eylemleri",
// "/building/meetings/regular/accounts": "Düzenli Toplantı Accounts",
// "/building/meetings/ergunt/actions": "Ergunt Toplantı Eylemleri",
// "/building/meetings/ergunt/accounts": "Ergunt Toplantı Accounts",
// "/building/meetings/invited/attendance": "Toplantı Davetli Katılımlar",
// };
const menuTranslationTr = { const menuTranslationTr = {
// New menu // New menu
"/dashboard": [ "/dashboard": [
@ -24,14 +5,14 @@ const menuTranslationTr = {
{ value: "Panel", key: "dashboard" }, { value: "Panel", key: "dashboard" },
{ value: "Panel", key: "dashboard" }, { value: "Panel", key: "dashboard" },
], ],
"/individual": [ "/people": [
{ value: "Bireysel", key: "individual" },
{ value: "Bireysel", key: "individual" }, { value: "Bireysel", key: "individual" },
{ value: "Bireysel", key: "individual" }, { value: "Bireysel", key: "individual" },
{ value: "Birey", key: "people" },
], ],
"/user": [ "/user": [
{ value: "Kullanıcı", key: "user" }, { value: "Bireysel", key: "individual" },
{ value: "Kullanıcı", key: "user" }, { value: "Bireysel", key: "individual" },
{ value: "Kullanıcı", key: "user" }, { value: "Kullanıcı", key: "user" },
], ],
"/build": [ "/build": [

View File

@ -237,54 +237,56 @@ function CreateFromComponentBase({
</Button> </Button>
</div> </div>
const mainComponent = <div className="mb-6 p-4 border rounded-md bg-blue-50 shadow-sm relative"> const mainComponent = selectedBuilding ? (
{/* Header with title and change button */} <div className="mb-6 p-4 border rounded-md bg-blue-50 shadow-sm relative">
<div className="flex justify-between items-center mb-2"> {/* Header with title and change button */}
<h3 className="text-lg font-medium text-blue-800"> <div className="flex justify-between items-center mb-2">
{buildingTranslations[language].selectedBuilding} <h3 className="text-lg font-medium text-blue-800">
</h3> {buildingTranslations[language].selectedBuilding}
<Button </h3>
onClick={() => router.push(`/panel/build`)} <Button
className="bg-red-100 hover:bg-red-200 text-red-700 p-1 h-8 w-8 rounded-full flex items-center justify-center" onClick={() => router.push(`/panel/build`)}
title={buildingTranslations[language].changeBuilding} className="bg-red-100 hover:bg-red-200 text-red-700 p-1 h-8 w-8 rounded-full flex items-center justify-center"
> title={buildingTranslations[language].changeBuilding}
<X size={16} /> >
</Button> <X size={16} />
</div> </Button>
{/* Building details grid */}
<div className="grid grid-cols-2 gap-2">
{/* UUID */}
<div>
<span className="font-semibold">UUID: </span>
{selectedBuilding.uu_id}
</div> </div>
{/* Building Name */} {/* Building details grid */}
<div> <div className="grid grid-cols-2 gap-2">
<span className="font-semibold">{buildingTranslations[language].buildName}: </span> {/* UUID */}
{selectedBuilding.build_name} <div>
</div> <span className="font-semibold">UUID: </span>
{selectedBuilding?.uu_id || '-'}
</div>
{/* Building Number */} {/* Building Name */}
<div> <div>
<span className="font-semibold">{buildingTranslations[language].buildNo}: </span> <span className="font-semibold">{buildingTranslations[language].buildName}: </span>
{selectedBuilding.build_no} {selectedBuilding?.build_name || '-'}
</div> </div>
{/* Address Code */} {/* Building Number */}
<div> <div>
<span className="font-semibold">{buildingTranslations[language].buildAddressCode}: </span> <span className="font-semibold">{buildingTranslations[language].buildNo}: </span>
{selectedBuilding.gov_address_code} {selectedBuilding?.build_no || '-'}
</div> </div>
{/* Max Floor */} {/* Address Code */}
<div> <div>
<span className="font-semibold">{buildingTranslations[language].buildMaxFloor}: </span> <span className="font-semibold">{buildingTranslations[language].buildAddressCode}: </span>
{selectedBuilding.max_floor} {selectedBuilding?.gov_address_code || '-'}
</div>
{/* Max Floor */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildMaxFloor}: </span>
{selectedBuilding?.max_floor || '-'}
</div>
</div> </div>
</div> </div>
</div> ) : null
return ( return (
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md"> <div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
@ -302,4 +304,4 @@ function CreateFromComponentBase({
} }
export const CreateFromBuildComponent = withCache(CreateFromComponentBase); export const CreateFromBuildComponent = withCache(CreateFromComponentBase);
export default withCache(CreateFromBuildComponent); export default CreateFromBuildComponent;

View File

@ -0,0 +1,307 @@
import React, { useState, useEffect } from "react";
import { apiPostFetcher } from "@/lib/fetcher";
import { useRouter } from "next/navigation";
import { withCache } from "@/components/mutual/context/cache/withCache";
import { Button } from "@/components/mutual/ui/button";
import { Form } from "@/components/mutual/ui/form";
import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
import { X } from "lucide-react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { buildPartsSchemaCreate, createEmptyValues } from "./schema";
import { buildPartsTranslations, translationsOfPage, buildingTranslations } from "./translations";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { NumberInput } from "@/components/mutual/formInputs/NumberInput";
import { StringInput } from "@/components/mutual/formInputs/StringInput";
import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput";
function CreateFromComponentBase({
onlineData,
cacheData,
cacheLoading,
cacheError,
refreshCache,
updateCache,
clearCache,
activePageUrl
}: {
onlineData?: any;
cacheData?: { [url: string]: any } | null;
cacheLoading?: boolean;
cacheError?: string | null;
refreshCache?: (url?: string) => Promise<void>;
updateCache?: (url: string, data: any) => Promise<void>;
clearCache?: (url: string) => Promise<void>;
activePageUrl: string;
}) {
const language: LanguageTypes = onlineData?.lang || 'en';
const router = useRouter();
const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`;
const [selectedBuilding, setSelectedBuilding] = useState<any>(null);
const [formError, setFormError] = useState<string>("");
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
const form = useForm({ resolver: zodResolver(buildPartsSchemaCreate), defaultValues: createEmptyValues });
useEffect(() => {
const checkCacheForSelectedBuild = async () => {
try {
const cachedData = await getCacheData("/build/list");
if (cachedData && cachedData.build) {
setSelectedBuilding(cachedData.build);
const formValues = form.getValues();
form.setValue("build_uu_id", cachedData.build.uu_id);
}
} catch (error) { console.error("Error checking cache for selected build:", error) }
};
checkCacheForSelectedBuild();
}, [form]);
useEffect(() => {
const fetchCacheDirectly = async () => {
if (!cacheLoaded) {
try {
const cachedData = await getCacheData(activePageUrl);
if (cachedData) {
const formValues = form.getValues();
const mergedData = {
build_uu_id: cachedData.build_uu_id || formValues.build_uu_id || null,
address_gov_code: cachedData.address_gov_code || formValues.address_gov_code || "",
part_no: cachedData.part_no || formValues.part_no || 0,
part_level: cachedData.part_level || formValues.part_level || 0,
part_code: cachedData.part_code || formValues.part_code || "",
part_gross_size: cachedData.part_gross_size || formValues.part_gross_size || 0,
part_net_size: cachedData.part_net_size || formValues.part_net_size || 0,
default_accessory: cachedData.default_accessory || formValues.default_accessory || "0",
human_livable: cachedData.human_livable || formValues.human_livable || true,
due_part_key: cachedData.due_part_key || formValues.due_part_key || "",
part_direction_uu_id: cachedData.part_direction_uu_id || formValues.part_direction_uu_id || null,
};
form.reset(mergedData);
}
setCacheLoaded(true);
if (refreshCache) { refreshCache(activePageUrl) }
} catch (error) { console.error("Error fetching cache directly:", error) }
}
};
fetchCacheDirectly();
}, []);
const handleFieldBlur = async (fieldName: string, value: any) => {
if (value) {
try {
const currentValues = form.getValues();
const updatedData = { ...currentValues, [fieldName]: value };
await setCacheData(activePageUrl, updatedData);
if (updateCache) { updateCache(activePageUrl, updatedData) }
} catch (error) { console.error("Error updating cache:", error) }
}
};
const onSubmit = async (data: any) => {
setFormError("");
if (!data.build_uu_id && selectedBuilding) { data.build_uu_id = selectedBuilding.uu_id }
if (!data.build_uu_id) {
const errorMessage = language === 'en' ? "Missing building ID. Please select a building first." : "Bina ID'si eksik. Lütfen önce bir bina seçin.";
console.error('errorMessage', errorMessage);
setFormError(errorMessage);
return;
}
console.log("Form submitted with data:", data);
try {
const response = await apiPostFetcher<any>({ url: `/api/parts/create`, isNoCache: true, body: data });
await clearCacheData(activePageUrl);
if (clearCache) { clearCache(activePageUrl) }
if (response.success) { form.reset(createEmptyValues); router.push(listUrl) }
} catch (error) {
console.error("Error submitting form:", error);
setFormError(language === 'en' ? "Error submitting form" : "Form gönderilirken hata oluştu");
}
}
const formComponent = <>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* ----- Basic Information ----- */}
{/* Address Government Code */}
<StringInput
control={form.control}
name="address_gov_code"
label={buildPartsTranslations[language].address_gov_code}
placeholder={buildPartsTranslations[language].address_gov_code}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Part Identification ----- */}
{/* Part Number */}
<NumberInput
control={form.control}
name="part_no"
label={buildPartsTranslations[language].part_no}
placeholder={buildPartsTranslations[language].part_no}
onBlurCallback={handleFieldBlur}
/>
{/* Part Level */}
<NumberInput
control={form.control}
name="part_level"
label={buildPartsTranslations[language].part_level}
placeholder={buildPartsTranslations[language].part_level}
onBlurCallback={handleFieldBlur}
/>
{/* Part Code */}
<StringInput
control={form.control}
name="part_code"
label={buildPartsTranslations[language].part_code}
placeholder={buildPartsTranslations[language].part_code}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Size Information ----- */}
{/* Part Gross Size */}
<NumberInput
control={form.control}
name="part_gross_size"
label={buildPartsTranslations[language].part_gross_size}
placeholder={buildPartsTranslations[language].part_gross_size}
onBlurCallback={handleFieldBlur}
/>
{/* Part Net Size */}
<NumberInput
control={form.control}
name="part_net_size"
label={buildPartsTranslations[language].part_net_size}
placeholder={buildPartsTranslations[language].part_net_size}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Additional Properties ----- */}
{/* Default Accessory */}
<StringInput
control={form.control}
name="default_accessory"
label={buildPartsTranslations[language].default_accessory}
placeholder={buildPartsTranslations[language].default_accessory}
onBlurCallback={handleFieldBlur}
/>
{/* Human Livable */}
<CheckBoxInput
control={form.control}
name="human_livable"
label={buildPartsTranslations[language].human_livable}
onBlurCallback={handleFieldBlur}
/>
{/* Due Part Key */}
<StringInput
control={form.control}
name="due_part_key"
label={buildPartsTranslations[language].due_part_key}
placeholder={buildPartsTranslations[language].due_part_key}
onBlurCallback={handleFieldBlur}
/>
{/* Part Direction UUID */}
<StringInput
control={form.control}
name="part_direction_uu_id"
label={buildPartsTranslations[language].part_direction_uu_id}
placeholder={buildPartsTranslations[language].part_direction_uu_id}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Submit Button ----- */}
<Button type="submit" className="w-full mt-8">
{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}
</Button>
</form>
</Form>
</>
const noSelectedBuildingComponent = <div className="mb-6 p-4 border rounded-md bg-yellow-50 text-center">
<div className="mb-4 text-gray-600">
{buildingTranslations[language].noSelectedBuilding}
</div>
<Button
onClick={() => router.push(`/panel/build`)}
className="bg-blue-600 text-white hover:bg-blue-700"
>
{buildingTranslations[language].selectBuilding}
</Button>
</div>
const mainComponent = selectedBuilding ? (
<div className="mb-6 p-4 border rounded-md bg-blue-50 shadow-sm relative">
{/* Header with title and change button */}
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-medium text-blue-800">
{buildingTranslations[language].selectedBuilding}
</h3>
<Button
onClick={() => router.push(`/panel/build`)}
className="bg-red-100 hover:bg-red-200 text-red-700 p-1 h-8 w-8 rounded-full flex items-center justify-center"
title={buildingTranslations[language].changeBuilding}
>
<X size={16} />
</Button>
</div>
{/* Building details grid */}
<div className="grid grid-cols-2 gap-2">
{/* UUID */}
<div>
<span className="font-semibold">UUID: </span>
{selectedBuilding?.uu_id || '-'}
</div>
{/* Building Name */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildName}: </span>
{selectedBuilding?.build_name || '-'}
</div>
{/* Building Number */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildNo}: </span>
{selectedBuilding?.build_no || '-'}
</div>
{/* Address Code */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildAddressCode}: </span>
{selectedBuilding?.gov_address_code || '-'}
</div>
{/* Max Floor */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildMaxFloor}: </span>
{selectedBuilding?.max_floor || '-'}
</div>
</div>
</div>
) : null
return (
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
{/* back to list button */}
<div className="flex justify-between items-center mb-6">
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
</div>
<h2 className="text-2xl font-bold mb-6">{translationsOfPage[language].title}</h2>
{/* ===== BUILDING SELECTION SECTION ===== */}
{selectedBuilding ? mainComponent : noSelectedBuildingComponent}
{/* ===== FORM SECTION ===== */}
{selectedBuilding && formComponent}
</div>
);
}
export const CreateFromBuildComponent = withCache(CreateFromComponentBase);
export default CreateFromBuildComponent;

View File

@ -0,0 +1,212 @@
import React, { useState, useEffect } from "react";
import { apiPostFetcher } from "@/lib/fetcher";
import { useRouter } from "next/navigation";
import { withCache } from "@/components/mutual/context/cache/withCache";
import { Button } from "@/components/mutual/ui/button";
import { Form } from "@/components/mutual/ui/form";
import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { buildPartsSchema, updateEmptyValues } from "./schema";
import { buildPartsTranslations, translationsOfPage } from "./translations";
import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput";
import { NumberInput } from "@/components/mutual/formInputs/NumberInput";
import { StringInput } from "@/components/mutual/formInputs/StringInput";
function UpdateFromComponentBase({
onlineData,
cacheData,
cacheLoading,
cacheError,
refreshCache,
updateCache,
clearCache,
activePageUrl
}: {
onlineData?: any;
cacheData?: { [url: string]: any } | null;
cacheLoading?: boolean;
cacheError?: string | null;
refreshCache?: (url?: string) => Promise<void>;
updateCache?: (url: string, data: any) => Promise<void>;
clearCache?: (url: string) => Promise<void>;
activePageUrl: string;
}) {
const language: LanguageTypes = onlineData?.lang || 'en';
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
const [partUUID, setPartUUID] = useState<string>(""); const router = useRouter();
const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`;
const form = useForm({ resolver: zodResolver(buildPartsSchema), mode: "onSubmit", defaultValues: updateEmptyValues });
useEffect(() => {
const fetchData = async () => {
if (!cacheLoaded) {
try {
const cachedData = await getCacheData(activePageUrl);
if (cachedData) {
const formValues = form.getValues();
const mergedData = {
uu_id: "",
address_gov_code: cachedData.address_gov_code || formValues.address_gov_code || "",
part_no: cachedData.part_no ?? formValues.part_no ?? 0,
part_level: cachedData.part_level ?? formValues.part_level ?? 0,
part_code: cachedData.part_code || formValues.part_code || "",
part_gross_size: cachedData.part_gross_size ?? formValues.part_gross_size ?? 0,
part_net_size: cachedData.part_net_size ?? formValues.part_net_size ?? 0,
default_accessory: cachedData.default_accessory || formValues.default_accessory || "0",
human_livable: cachedData.human_livable ?? formValues.human_livable ?? true,
due_part_key: cachedData.due_part_key || formValues.due_part_key || "",
part_direction_uu_id: cachedData.part_direction_uu_id ?? formValues.part_direction_uu_id ?? null
};
form.reset(mergedData);
setPartUUID(cachedData.uu_id);
}
setCacheLoaded(true);
if (refreshCache) { refreshCache(activePageUrl); }
} catch (error) { console.error("Error fetching cache:", error); }
}
};
fetchData();
}, []);
const handleFieldBlur = async (fieldName: string, value: any) => {
if (value !== undefined) {
try {
const currentValues = form.getValues();
const updatedData = { ...currentValues, [fieldName]: value };
await setCacheData(activePageUrl, updatedData);
if (updateCache) { updateCache(activePageUrl, updatedData); }
} catch (error) { console.error("Error updating cache:", error); }
}
};
const onSubmit = async (data: any) => {
try {
console.log('Form data received:', data);
const formattedData = { ...data, uuid: partUUID };
const response = await apiPostFetcher<any>({
url: `/api/parts/update/${partUUID}`,
isNoCache: true,
body: formattedData,
});
await clearCacheData(activePageUrl);
if (clearCache) { clearCache(activePageUrl); }
if (response.success) { form.reset(updateEmptyValues); router.push(listUrl) }
} catch (error) { console.error("Error submitting form:", error); }
}
return (
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
{/* back to list button */}
<div className="flex justify-between items-center mb-6">
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
</div>
<h2 className="text-2xl font-bold mb-3">{translationsOfPage[language].title}</h2>
<h4 className="text-sm text-gray-500 mb-6">UUID: {partUUID}</h4>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* Address Government Code */}
<StringInput
control={form.control}
name="address_gov_code"
label={buildPartsTranslations[language].address_gov_code}
placeholder={buildPartsTranslations[language].address_gov_code}
onBlurCallback={handleFieldBlur}
/>
{/* Part Number */}
<NumberInput
control={form.control}
name="part_no"
label={buildPartsTranslations[language].part_no}
placeholder={buildPartsTranslations[language].part_no}
onBlurCallback={handleFieldBlur}
/>
{/* Part Level */}
<NumberInput
control={form.control}
name="part_level"
label={buildPartsTranslations[language].part_level}
placeholder={buildPartsTranslations[language].part_level}
onBlurCallback={handleFieldBlur}
/>
{/* Part Code */}
<StringInput
control={form.control}
name="part_code"
label={buildPartsTranslations[language].part_code}
placeholder={buildPartsTranslations[language].part_code}
onBlurCallback={handleFieldBlur}
/>
{/* Part Gross Size */}
<NumberInput
control={form.control}
name="part_gross_size"
label={buildPartsTranslations[language].part_gross_size}
placeholder={buildPartsTranslations[language].part_gross_size}
onBlurCallback={handleFieldBlur}
/>
{/* Part Net Size */}
<NumberInput
control={form.control}
name="part_net_size"
label={buildPartsTranslations[language].part_net_size}
placeholder={buildPartsTranslations[language].part_net_size}
onBlurCallback={handleFieldBlur}
/>
{/* Default Accessory */}
<StringInput
control={form.control}
name="default_accessory"
label={buildPartsTranslations[language].default_accessory}
placeholder={buildPartsTranslations[language].default_accessory}
onBlurCallback={handleFieldBlur}
/>
{/* Human Livable */}
<CheckBoxInput
control={form.control}
name="human_livable"
label={buildPartsTranslations[language].human_livable}
onBlurCallback={handleFieldBlur}
/>
{/* Due Part Key */}
<StringInput
control={form.control}
name="due_part_key"
label={buildPartsTranslations[language].due_part_key}
placeholder={buildPartsTranslations[language].due_part_key}
onBlurCallback={handleFieldBlur}
/>
{/* Part Direction UUID */}
<StringInput
control={form.control}
name="part_direction_uu_id"
label={buildPartsTranslations[language].part_direction_uu_id}
placeholder={buildPartsTranslations[language].part_direction_uu_id}
onBlurCallback={handleFieldBlur}
/>
<Button type="submit" className="w-full">{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}</Button>
</form>
</Form>
</div>
);
}
export const UpdateFromComponent = withCache(UpdateFromComponentBase);
export default withCache(UpdateFromComponentBase);

View File

@ -0,0 +1,142 @@
'use client';
import React from "react";
import LoadingContent from "@/components/mutual/loader/component";
import { useRouter } from "next/navigation";
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, flexRender, ColumnDef } from "@tanstack/react-table";
import { Button } from "@/components/mutual/ui/button";
import { getCacheData } from "@/components/mutual/context/cache/context";
import { API_BASE_URL } from "@/config/config";
import { useTableData } from "@/hooks/useTableData";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { DashboardPageProps } from "@/validations/mutual/table/validations";
import { BuildPartsSchemaInterface, listTranslations } from "../schema";
import DataTable from "./dataTable";
import TableForm from "./tableForm";
import MobilePaginationControls from "./mobilePaginationControls";
import TableHeader from "./tableHeader";
import { columnHelper, retrieveColumns } from "./columns";
const CompanyListPage: React.FC<DashboardPageProps> = React.memo((props) => {
const router = useRouter();
const activePageUrl = props.activePageUrl || '';
const language = props.onlineData?.lang as LanguageTypes || 'en';
const t = listTranslations[language];
const apiUrl = `${API_BASE_URL}/parts/list`;
const [selectedBuilding, setSelectedBuilding] = React.useState<any>(null);
const {
tableData, isLoading, error, form, pagination, apiPagination, setSorting, handleSortingChange, handleSelectChange,
handleFirstPage, handlePreviousPage, handleNextPage, getSortingIcon, handleFormSubmit,
isPreviousDisabled, isNextDisabled, pageSizeOptions, renderPageOptions, sorting, // Disabled states
} = useTableData({
apiUrl,
mapFormToRequestBody: (values) => {
const requestBody = {
page: values.page, size: values.size, orderField: values.orderField, orderType: values.orderType,
query: selectedBuilding ? { build_uu_id: selectedBuilding.uu_id } : {}
}; return requestBody;
}
});
React.useEffect(() => {
const checkCacheForSelectedBuild = async () => {
try {
const cachedData = await getCacheData("/build/list");
if (cachedData && cachedData.build) { setSelectedBuilding(cachedData.build) }
} catch (error) { console.error("Error checking cache for selected build:", error) }
};
checkCacheForSelectedBuild();
}, []);
React.useEffect(() => {
if (selectedBuilding) { form.setValue("page", 1); form.setValue("query", { build_uu_id: selectedBuilding.uu_id }) }
}, [selectedBuilding, form]);
const columnsArray = retrieveColumns(language, activePageUrl, router);
const columns = React.useMemo(() => columnsArray, [columnHelper]) as ColumnDef<BuildPartsSchemaInterface>[];
const table = useReactTable({
data: tableData,
columns,
state: { sorting, pagination },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
manualPagination: true,
pageCount: apiPagination.totalPages || 1,
})
const dataTableComponent = <DataTable
table={table}
tableData={tableData}
isLoading={isLoading}
handleSortingChange={handleSortingChange}
getSortingIcon={getSortingIcon}
flexRender={flexRender}
t={t}
selectedBuilding={selectedBuilding}
router={router}
/>
const noSelectedBuildingComponent = <div className="p-8 text-center">
<div className="mb-4 text-gray-600">{t.noSelectedBuilding || "No building selected. Please select a building first."}</div>
<Button onClick={() => router.push(`/panel/build`)} className="bg-blue-600 text-white hover:bg-blue-700">
{t.selectBuilding || "Select Building"}
</Button>
</div>
return (
isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
<div className="flex justify-between items-center p-4">
<TableHeader
title={t.dataTable}
description={t.tableWithApiData}
isLoading={isLoading}
error={error}
t={t}
/>
<Button onClick={() => router.push(`/panel/${activePageUrl}/create`)} className="bg-primary text-white hover:bg-primary/90">
<span className="mr-2">{t.createNewBuildPart}</span>
<span className="sr-only">{t.createNewBuildPart}</span>
</Button>
</div>
<TableForm
form={form}
handleFormSubmit={handleFormSubmit}
handleSelectChange={handleSelectChange}
renderPageOptions={renderPageOptions}
pageSizeOptions={pageSizeOptions}
apiPagination={apiPagination}
handleFirstPage={handleFirstPage}
handlePreviousPage={handlePreviousPage}
handleNextPage={handleNextPage}
isPreviousDisabled={isPreviousDisabled}
isNextDisabled={isNextDisabled}
t={t}
/>
{/* Mobile pagination controls - only visible on small screens */}
<MobilePaginationControls
handlePreviousPage={handlePreviousPage}
handleNextPage={handleNextPage}
isPreviousDisabled={isPreviousDisabled}
isNextDisabled={isNextDisabled}
t={t}
/>
{selectedBuilding ? dataTableComponent : noSelectedBuildingComponent}
</div >
);
});
export default CompanyListPage;

View File

@ -0,0 +1,104 @@
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
flexRender,
createColumnHelper,
ColumnDef,
} from "@tanstack/react-table";
import { BuildPartsSchemaInterface } from "../schema";
import { buildPartsTranslations, translationsOfPage } from '../translations';
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { Button } from "@/components/mutual/ui/button";
import { Pencil } from "lucide-react";
import { setCacheData } from "@/components/mutual/context/cache/context";
const columnHelper = createColumnHelper<BuildPartsSchemaInterface>();
function retrieveColumns(language: LanguageTypes, activePageUrl: string, router: any) {
const handleEditRow = async (row: BuildPartsSchemaInterface) => {
try {
await setCacheData(`${activePageUrl}/update`, row);
router.push(`/panel/${activePageUrl}/update`);
} catch (error) { console.error('Error storing row data in cache:', error) }
};
const actionButtonGroup = (info: any) => (
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.preventDefault();
console.log('Edit button clicked');
handleEditRow && handleEditRow(info.row.original);
}}
className="h-8 w-8 p-0"
>
<Pencil className="h-4 w-4" />
<span className="sr-only">Edit</span>
</Button>
);
const columnsArray = [
columnHelper.display({
id: 'actions',
header: () => <span>{translationsOfPage[language].actionButtonGroup}</span>,
cell: (info) => { return (actionButtonGroup(info)) }
}),
columnHelper.accessor('address_gov_code', {
cell: info => info.getValue(),
header: () => <span>{buildPartsTranslations[language].address_gov_code}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('part_no', {
cell: info => String(info.getValue()),
header: () => <span>{buildPartsTranslations[language].part_no}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('part_level', {
cell: info => String(info.getValue()),
header: () => <span>{buildPartsTranslations[language].part_level}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('part_code', {
cell: info => info.getValue(),
header: () => <span>{buildPartsTranslations[language].part_code}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('part_gross_size', {
cell: info => String(info.getValue()),
header: () => <span>{buildPartsTranslations[language].part_gross_size}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('part_net_size', {
cell: info => String(info.getValue()),
header: () => <span>{buildPartsTranslations[language].part_net_size}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('default_accessory', {
cell: info => info.getValue(),
header: () => <span>{buildPartsTranslations[language].default_accessory}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('human_livable', {
cell: info => info.getValue() ? 'Yes' : 'No',
header: () => <span>{buildPartsTranslations[language].human_livable}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('due_part_key', {
cell: info => info.getValue(),
header: () => <span>{buildPartsTranslations[language].due_part_key}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('part_direction_uu_id', {
cell: info => info.getValue() || '',
header: () => <span>{buildPartsTranslations[language].part_direction_uu_id}</span>,
footer: info => info.column.id
}),
]
return columnsArray;
}
export {
retrieveColumns,
columnHelper
}

View File

@ -0,0 +1,129 @@
'use client';
import React from "react";
import { Button } from "@/components/mutual/ui/button";
import { X } from "lucide-react";
import { useReactTable } from "@tanstack/react-table";
import { flexRender } from "@tanstack/react-table";
import { BuildPartsSchemaInterface } from "../schema";
import { Translations } from "./types";
interface DataTableProps {
table: ReturnType<typeof useReactTable>;
tableData: BuildPartsSchemaInterface[];
isLoading: boolean;
handleSortingChange: (columnId: string) => void;
getSortingIcon: (columnId: string) => React.ReactNode;
flexRender: typeof flexRender;
t: Translations;
selectedBuilding: any | null;
router: any;
}
const DataTable: React.FC<DataTableProps> = React.memo(({
table,
tableData,
isLoading,
handleSortingChange,
getSortingIcon,
flexRender,
t,
selectedBuilding,
router,
}) => {
return (
<div className="overflow-x-auto relative">
{/* Semi-transparent loading overlay that preserves interactivity */}
{isLoading && (
<div className="absolute inset-0 bg-white bg-opacity-60 flex items-center justify-center z-10">
{/* We don't put anything here as we already have the loading indicator in the header */}
</div>
)}
{selectedBuilding && (
<div className="mb-4 p-4 border rounded-md bg-blue-50 shadow-sm relative">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-medium text-blue-800">{t.selectedBuilding}</h3>
<Button
onClick={() => router.push(`/panel/build`)}
className="bg-red-100 hover:bg-red-200 text-red-700 p-1 h-8 w-8 rounded-full flex items-center justify-center"
title={t.changeBuilding || "Change Building"}
>
<X size={16} />
</Button>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<span className="font-semibold">UUID: </span>
{selectedBuilding.uu_id}
</div>
<div>
<span className="font-semibold">{t.buildName}: </span>
{selectedBuilding.build_name}
</div>
<div>
<span className="font-semibold">{t.buildNo}: </span>
{selectedBuilding.build_no}
</div>
<div>
<span className="font-semibold">{t.buildAddressCode}: </span>
{selectedBuilding.gov_address_code}
</div>
<div>
<span className="font-semibold">{t.buildMaxFloor}: </span>
{selectedBuilding.max_floor}
</div>
</div>
</div>
)}
<table className="min-w-full divide-y divide-gray-200">
<caption className="sr-only">{t.dataTable}</caption>
<thead className="bg-gray-50">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSortingChange(header.column.id)}
aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined}
scope="col"
>
<div className="flex items-center space-x-1">
{flexRender(header.column.columnDef.header, header.getContext())}
<span>{getSortingIcon(header.column.id)}</span>
</div>
</th>
);
})}
</tr>
))}
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{tableData.length === 0 && !isLoading ? (
<tr>
<td colSpan={table.getAllColumns().length} className="px-6 py-4 text-center text-gray-500">
{t.noDataAvailable}
</td>
</tr>
) : (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-gray-50">
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
))
)}
</tbody>
</table>
</div>
);
});
export default DataTable;

View File

@ -0,0 +1,34 @@
import { MobilePaginationControlsProps } from "@/validations/mutual/table/validations";
import { Button } from "@/components/mutual/ui/button";
const MobilePaginationControls: React.FC<MobilePaginationControlsProps> = ({
handlePreviousPage,
handleNextPage,
isPreviousDisabled,
isNextDisabled,
t
}) => {
return (
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:hidden">
<div className="flex-1 flex justify-between">
<Button
onClick={handlePreviousPage}
disabled={isPreviousDisabled()}
variant="outline"
size="sm"
aria-label="Go to previous page"
>{t.previous}</Button>
<Button
onClick={handleNextPage}
disabled={isNextDisabled()}
variant="outline"
size="sm"
className="ml-2"
aria-label="Go to next page"
>{t.next}</Button>
</div>
</div>
);
};
export default MobilePaginationControls;

View File

@ -0,0 +1,152 @@
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/mutual/ui/form";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select";
import { UseFormReturn } from "react-hook-form";
import { Translations } from "./types";
import { Button } from "@/components/mutual/ui/button";
interface TableFormProps {
form: UseFormReturn<any>;
handleFormSubmit: (e: React.FormEvent) => void;
handleSelectChange: (value: string, field: { onChange: (value: number) => void }) => void;
renderPageOptions: () => { key: string | number; value: string; label: string }[];
pageSizeOptions: number[];
apiPagination: {
size: number;
page: number;
totalCount: number;
totalPages: number;
pageCount: number;
};
handleFirstPage: () => void;
handlePreviousPage: () => void;
handleNextPage: () => void;
isPreviousDisabled: () => boolean;
isNextDisabled: () => boolean;
t: Translations;
}
const TableForm: React.FC<TableFormProps> = ({
form,
handleFormSubmit,
handleSelectChange,
renderPageOptions,
pageSizeOptions,
apiPagination,
handleFirstPage,
handlePreviousPage,
handleNextPage,
isPreviousDisabled,
isNextDisabled,
t
}) => {
return (
<div className="p-4 border-b border-gray-200">
<Form {...form}>
<form onSubmit={handleFormSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="md:col-span-1">
<FormField
control={form.control}
name="page"
render={({ field }) => (
<FormItem>
<FormLabel>{t.page}</FormLabel>
<Select
value={field.value.toString()}
onValueChange={(value: string) => handleSelectChange(value, field)}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t.selectPage} />
</SelectTrigger>
</FormControl>
<SelectContent>
{renderPageOptions().length > 0 ? (
renderPageOptions().map((option: { key: string | number; value: string; label: string }) => (
<SelectItem key={option.key} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<SelectItem key="1" value="1">1</SelectItem>
)}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="md:col-span-1">
<FormField
control={form.control}
name="size"
render={({ field }) => (
<FormItem>
<FormLabel>{t.size}</FormLabel>
<Select
value={field.value.toString()}
onValueChange={(value: string) => handleSelectChange(value, field)}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t.selectSize} />
</SelectTrigger>
</FormControl>
<SelectContent>
{pageSizeOptions.map((size: number) => (
<SelectItem key={size} value={size.toString()}>
{size}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="md:col-span-1 flex items-end">
<p className="text-sm text-gray-700">
{t.page}: <span className="font-medium">{apiPagination.page}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.totalPages}</span> ·
{t.size}: <span className="font-medium">{apiPagination.pageCount}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.size}</span> ·
{t.total}: <span className="font-medium">{apiPagination.totalCount}</span> {t.items}
</p>
</div>
<div className="md:col-span-1 flex items-end justify-end space-x-2">
<Button
onClick={handleFirstPage}
disabled={isPreviousDisabled()}
variant="outline"
size="sm"
aria-label="Go to first page"
>{t.first}</Button>
<Button
onClick={handlePreviousPage}
disabled={isPreviousDisabled()}
variant="outline"
size="sm"
aria-label="Go to previous page"
>{t.previous}</Button>
<Button
onClick={handleNextPage}
disabled={isNextDisabled()}
variant="outline"
size="sm"
aria-label="Go to next page"
>{t.next}</Button>
{/* <Button type="submit" disabled={isLoading} size="sm">Fetch</Button> */}
</div>
</form>
</Form>
</div>
);
};
export default TableForm;

View File

@ -0,0 +1,24 @@
import { TableHeaderProps, ErrorDisplayProps } from "@/validations/mutual/table/validations";
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
return <div className="text-red-500">{message}</div>;
};
const TableHeader: React.FC<TableHeaderProps> = ({ title, description, isLoading, error, t }) => {
return (
<div className="p-4 border-b border-gray-200">
<div className="flex justify-between items-center">
<div>
<h2 className="text-lg font-semibold text-gray-800">{title}</h2>
<p className="text-sm text-gray-500">{description}</p>
</div>
{/* {isLoading && <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />} */}
{error && <ErrorDisplay message={error} />}
</div>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,15 @@
import { Translations as BaseTranslations } from "@/validations/mutual/table/validations";
// Extended Translations interface to include building-related translations
interface Translations extends BaseTranslations {
selectedBuilding: string;
buildName: string;
buildNo: string;
buildAddressCode: string;
buildMaxFloor: string;
noSelectedBuilding: string;
selectBuilding: string;
changeBuilding: string;
}
export type { Translations };

View File

@ -0,0 +1,147 @@
import { LanguageTypes } from "@/validations/mutual/language/validations";
import * as z from "zod";
/**
* Zod schema for BuildParts
* Corresponds to the BuildParts class in Python
*/
const buildPartsSchema = z.object({
uu_id: z.string(),
build_uu_id: z.string().nullable(),
address_gov_code: z.string().describe("Goverment Door Code"),
part_no: z.number().int().describe("Part Number"),
part_level: z.number().int().describe("Building Part Level"),
part_code: z.string().describe("Part Code"),
part_gross_size: z.number().int().describe("Part Gross Size"),
part_net_size: z.number().int().describe("Part Net Size"),
default_accessory: z.string().describe("Default Accessory"),
human_livable: z.boolean().describe("Human Livable"),
due_part_key: z.string().describe("Constant Payment Group"),
part_direction_uu_id: z.string().nullable().describe("Part Direction UUID"),
});
const buildPartsSchemaCreate = buildPartsSchema.omit({ uu_id: true }).extend({
part_no: z.number().int().default(0),
part_level: z.number().int().default(0),
part_code: z.string().default(""),
part_gross_size: z.number().int().default(0),
part_net_size: z.number().int().default(0),
default_accessory: z.string().default("0"),
human_livable: z.boolean().default(true),
due_part_key: z.string().default(""),
});
type BuildPartsInterface = z.infer<typeof buildPartsSchema>;
type BuildPartsCreateInterface = z.infer<typeof buildPartsSchemaCreate>;
interface BuildPartsSchemaInterface {
uu_id: string;
build_uu_id: string | null;
address_gov_code: string;
part_no: number;
part_level: number;
part_code: string;
part_gross_size: number;
part_net_size: number;
default_accessory: string;
human_livable: boolean;
due_part_key: string;
part_direction_uu_id: string | null;
}
const createEmptyValues: BuildPartsCreateInterface = {
build_uu_id: null,
address_gov_code: "",
part_no: 0,
part_level: 0,
part_code: "",
part_gross_size: 0,
part_net_size: 0,
default_accessory: "0",
human_livable: true,
due_part_key: "",
part_direction_uu_id: null,
};
const updateEmptyValues: BuildPartsInterface = {
uu_id: "",
build_uu_id: null,
address_gov_code: "",
part_no: 0,
part_level: 0,
part_code: "",
part_gross_size: 0,
part_net_size: 0,
default_accessory: "0",
human_livable: true,
due_part_key: "",
part_direction_uu_id: null,
};
const listTranslations: Record<LanguageTypes, any> = {
en: {
createNew: "Create New",
createNewBuildPart: "Create New Build Part",
dataTable: "Data Table",
tableWithApiData: "Table with API Data",
loading: "Loading...",
noDataAvailable: "No data available",
page: "Page",
size: "Size",
total: "Total",
items: "items",
first: "First",
previous: "Previous",
next: "Next",
selectPage: "Select page",
selectSize: "Select size",
actionButtonGroup: "Actions",
selectedBuilding: "Selected Building",
buildName: "Building Name",
buildNo: "Building No",
buildAddressCode: "Building Address Code",
buildMaxFloor: "Building Max Floor",
noSelectedBuilding: "No building selected. Please select a building first.",
selectBuilding: "Select Building",
changeBuilding: "Change Building",
},
tr: {
createNew: "Yeni Oluştur",
createNewBuildPart: "Yeni Bina Parçası Oluştur",
dataTable: "Veri Tablosu",
tableWithApiData: "API Verili Tablo",
loading: "Yükleniyor...",
noDataAvailable: "Veri bulunamadı",
page: "Sayfa",
size: "Boyut",
total: "Toplam",
items: "öğe",
first: "İlk",
previous: "Önceki",
next: "Sonraki",
selectPage: "Sayfa seç",
selectSize: "Boyut seç",
actionButtonGroup: "Eylemler",
selectedBuilding: "Seçilen Bina",
buildName: "Bina Adı",
buildNo: "Bina No",
buildAddressCode: "Bina Adres Kodu",
buildMaxFloor: "Bina Max Kat",
noSelectedBuilding: "Bina seçilmedi. Lütfen önce bir bina seçin.",
selectBuilding: "Bina Seç",
changeBuilding: "Bina Değiştir",
},
};
export type {
BuildPartsInterface,
BuildPartsSchemaInterface,
BuildPartsCreateInterface,
};
export {
buildPartsSchema,
buildPartsSchemaCreate,
createEmptyValues,
updateEmptyValues,
listTranslations,
};

View File

@ -0,0 +1,70 @@
const buildPartsTranslations = {
tr: {
uu_id: "UUID",
build_uu_id: "Bina UUID",
address_gov_code: "Devlet Adres Kodu",
part_no: "Daire Numarası",
part_level: "Daire Katı",
part_code: "Daire Kodu",
part_gross_size: "Daire Brüt Boyutu",
part_net_size: "Daire Net Boyutu",
default_accessory: "Varsayılan Aksesuar",
human_livable: "İnsan Yaşanabilir",
due_part_key: "Sabit Ödeme Grubu",
part_direction_uu_id: "Daire Yön UUID",
},
en: {
uu_id: "UUID",
build_uu_id: "Building UUID",
address_gov_code: "Government Door Code",
part_no: "Part Number",
part_level: "Building Part Level",
part_code: "Part Code",
part_gross_size: "Part Gross Size",
part_net_size: "Part Net Size",
default_accessory: "Default Accessory",
human_livable: "Human Livable",
due_part_key: "Constant Payment Group",
part_direction_uu_id: "Part Direction UUID",
},
};
const translationsOfPage = {
tr: {
title: "Binaya Daire Oluştur",
updateTitle: "Binanın Dairesini Güncelle",
back2List: "Listeye Geri Dön",
actionButtonGroup: "İşlemler",
},
en: {
title: "Create Building Part",
updateTitle: "Update Building Part",
back2List: "Back to List",
actionButtonGroup: "Actions",
},
};
const buildingTranslations = {
en: {
selectedBuilding: "Selected Building",
buildName: "Building Name",
buildNo: "Building No",
buildAddressCode: "Address Code",
buildMaxFloor: "Max Floor",
noSelectedBuilding: "No building selected. Please select a building first.",
selectBuilding: "Select Building",
changeBuilding: "Change Building",
},
tr: {
selectedBuilding: "Seçili Bina",
buildName: "Bina Adı",
buildNo: "Bina No",
buildAddressCode: "Adres Kodu",
buildMaxFloor: "Maksimum Kat",
noSelectedBuilding: "Bina seçilmedi. Lütfen önce bir bina seçin.",
selectBuilding: "Bina Seç",
changeBuilding: "Binayı Değiştir",
},
};
export { buildPartsTranslations, translationsOfPage, buildingTranslations };

View File

@ -7,6 +7,8 @@ import UpdateFromBuildComponent from "./builds/superuser/UpdatePage";
import BuildPartsListPage from "./buildParts/superuser/main/ListPage"; import BuildPartsListPage from "./buildParts/superuser/main/ListPage";
import CreateFromBuildPartsComponent from "./buildParts/superuser/CreatePage"; import CreateFromBuildPartsComponent from "./buildParts/superuser/CreatePage";
import UpdateFromBuildPartsComponent from "./buildParts/superuser/UpdatePage"; import UpdateFromBuildPartsComponent from "./buildParts/superuser/UpdatePage";
import PeopleListPage from "./people/superuser/main/ListPage";
import CompanyListPage from "./company/superuser/main/ListPage";
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = { const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
"/dashboard": { DashboardPage: TableCardComponentImproved }, "/dashboard": { DashboardPage: TableCardComponentImproved },
@ -16,9 +18,12 @@ const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
"/build/parts": { DashboardPage: BuildPartsListPage }, "/build/parts": { DashboardPage: BuildPartsListPage },
"/build/parts/create": { DashboardPage: CreateFromBuildPartsComponent }, "/build/parts/create": { DashboardPage: CreateFromBuildPartsComponent },
"/build/parts/update": { DashboardPage: UpdateFromBuildPartsComponent }, "/build/parts/update": { DashboardPage: UpdateFromBuildPartsComponent },
"/people": { DashboardPage: DPage }, "/people": { DashboardPage: PeopleListPage },
"/people/create": { DashboardPage: DPage }, "/people/create": { DashboardPage: DPage },
"/people/update": { DashboardPage: DPage }, "/people/update": { DashboardPage: DPage },
"/company": { DashboardPage: CompanyListPage },
"/company/create": { DashboardPage: DPage },
"/company/update": { DashboardPage: DPage },
"/decision/book": { DashboardPage: DPage }, "/decision/book": { DashboardPage: DPage },
"/decision/book/create": { DashboardPage: DPage }, "/decision/book/create": { DashboardPage: DPage },
"/decision/book/update": { DashboardPage: DPage }, "/decision/book/update": { DashboardPage: DPage },

View File

@ -0,0 +1,305 @@
import React, { useState, useEffect } from "react";
import { apiPostFetcher } from "@/lib/fetcher";
import { useRouter } from "next/navigation";
import { withCache } from "@/components/mutual/context/cache/withCache";
import { Button } from "@/components/mutual/ui/button";
import { Form } from "@/components/mutual/ui/form";
import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
import { X } from "lucide-react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { buildPartsSchemaCreate, createEmptyValues } from "./schema";
import { buildPartsTranslations, translationsOfPage, buildingTranslations } from "./translations";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { NumberInput } from "@/components/mutual/formInputs/NumberInput";
import { StringInput } from "@/components/mutual/formInputs/StringInput";
import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput";
function CreateFromComponentBase({
onlineData,
cacheData,
cacheLoading,
cacheError,
refreshCache,
updateCache,
clearCache,
activePageUrl
}: {
onlineData?: any;
cacheData?: { [url: string]: any } | null;
cacheLoading?: boolean;
cacheError?: string | null;
refreshCache?: (url?: string) => Promise<void>;
updateCache?: (url: string, data: any) => Promise<void>;
clearCache?: (url: string) => Promise<void>;
activePageUrl: string;
}) {
const language: LanguageTypes = onlineData?.lang || 'en';
const router = useRouter();
const listUrl = `/panel/${activePageUrl?.replace("/create", "")}`;
const [selectedBuilding, setSelectedBuilding] = useState<any>(null);
const [formError, setFormError] = useState<string>("");
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
const form = useForm({ resolver: zodResolver(buildPartsSchemaCreate), defaultValues: createEmptyValues });
useEffect(() => {
const checkCacheForSelectedBuild = async () => {
try {
const cachedData = await getCacheData("/build/list");
if (cachedData && cachedData.build) {
setSelectedBuilding(cachedData.build);
const formValues = form.getValues();
form.setValue("build_uu_id", cachedData.build.uu_id);
}
} catch (error) { console.error("Error checking cache for selected build:", error) }
};
checkCacheForSelectedBuild();
}, [form]);
useEffect(() => {
const fetchCacheDirectly = async () => {
if (!cacheLoaded) {
try {
const cachedData = await getCacheData(activePageUrl);
if (cachedData) {
const formValues = form.getValues();
const mergedData = {
build_uu_id: cachedData.build_uu_id || formValues.build_uu_id || null,
address_gov_code: cachedData.address_gov_code || formValues.address_gov_code || "",
part_no: cachedData.part_no || formValues.part_no || 0,
part_level: cachedData.part_level || formValues.part_level || 0,
part_code: cachedData.part_code || formValues.part_code || "",
part_gross_size: cachedData.part_gross_size || formValues.part_gross_size || 0,
part_net_size: cachedData.part_net_size || formValues.part_net_size || 0,
default_accessory: cachedData.default_accessory || formValues.default_accessory || "0",
human_livable: cachedData.human_livable || formValues.human_livable || true,
due_part_key: cachedData.due_part_key || formValues.due_part_key || "",
part_direction_uu_id: cachedData.part_direction_uu_id || formValues.part_direction_uu_id || null,
};
form.reset(mergedData);
}
setCacheLoaded(true);
if (refreshCache) { refreshCache(activePageUrl) }
} catch (error) { console.error("Error fetching cache directly:", error) }
}
};
fetchCacheDirectly();
}, []);
const handleFieldBlur = async (fieldName: string, value: any) => {
if (value) {
try {
const currentValues = form.getValues();
const updatedData = { ...currentValues, [fieldName]: value };
await setCacheData(activePageUrl, updatedData);
if (updateCache) { updateCache(activePageUrl, updatedData) }
} catch (error) { console.error("Error updating cache:", error) }
}
};
const onSubmit = async (data: any) => {
setFormError("");
if (!data.build_uu_id && selectedBuilding) { data.build_uu_id = selectedBuilding.uu_id }
if (!data.build_uu_id) {
const errorMessage = language === 'en' ? "Missing building ID. Please select a building first." : "Bina ID'si eksik. Lütfen önce bir bina seçin.";
console.error('errorMessage', errorMessage);
setFormError(errorMessage);
return;
}
console.log("Form submitted with data:", data);
try {
const response = await apiPostFetcher<any>({ url: `/api/parts/create`, isNoCache: true, body: data });
await clearCacheData(activePageUrl);
if (clearCache) { clearCache(activePageUrl) }
if (response.success) { form.reset(createEmptyValues); router.push(listUrl) }
} catch (error) {
console.error("Error submitting form:", error);
setFormError(language === 'en' ? "Error submitting form" : "Form gönderilirken hata oluştu");
}
}
const formComponent = <>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* ----- Basic Information ----- */}
{/* Address Government Code */}
<StringInput
control={form.control}
name="address_gov_code"
label={buildPartsTranslations[language].address_gov_code}
placeholder={buildPartsTranslations[language].address_gov_code}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Part Identification ----- */}
{/* Part Number */}
<NumberInput
control={form.control}
name="part_no"
label={buildPartsTranslations[language].part_no}
placeholder={buildPartsTranslations[language].part_no}
onBlurCallback={handleFieldBlur}
/>
{/* Part Level */}
<NumberInput
control={form.control}
name="part_level"
label={buildPartsTranslations[language].part_level}
placeholder={buildPartsTranslations[language].part_level}
onBlurCallback={handleFieldBlur}
/>
{/* Part Code */}
<StringInput
control={form.control}
name="part_code"
label={buildPartsTranslations[language].part_code}
placeholder={buildPartsTranslations[language].part_code}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Size Information ----- */}
{/* Part Gross Size */}
<NumberInput
control={form.control}
name="part_gross_size"
label={buildPartsTranslations[language].part_gross_size}
placeholder={buildPartsTranslations[language].part_gross_size}
onBlurCallback={handleFieldBlur}
/>
{/* Part Net Size */}
<NumberInput
control={form.control}
name="part_net_size"
label={buildPartsTranslations[language].part_net_size}
placeholder={buildPartsTranslations[language].part_net_size}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Additional Properties ----- */}
{/* Default Accessory */}
<StringInput
control={form.control}
name="default_accessory"
label={buildPartsTranslations[language].default_accessory}
placeholder={buildPartsTranslations[language].default_accessory}
onBlurCallback={handleFieldBlur}
/>
{/* Human Livable */}
<CheckBoxInput
control={form.control}
name="human_livable"
label={buildPartsTranslations[language].human_livable}
onBlurCallback={handleFieldBlur}
/>
{/* Due Part Key */}
<StringInput
control={form.control}
name="due_part_key"
label={buildPartsTranslations[language].due_part_key}
placeholder={buildPartsTranslations[language].due_part_key}
onBlurCallback={handleFieldBlur}
/>
{/* Part Direction UUID */}
<StringInput
control={form.control}
name="part_direction_uu_id"
label={buildPartsTranslations[language].part_direction_uu_id}
placeholder={buildPartsTranslations[language].part_direction_uu_id}
onBlurCallback={handleFieldBlur}
/>
{/* ----- Submit Button ----- */}
<Button type="submit" className="w-full mt-8">
{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}
</Button>
</form>
</Form>
</>
const noSelectedBuildingComponent = <div className="mb-6 p-4 border rounded-md bg-yellow-50 text-center">
<div className="mb-4 text-gray-600">
{buildingTranslations[language].noSelectedBuilding}
</div>
<Button
onClick={() => router.push(`/panel/build`)}
className="bg-blue-600 text-white hover:bg-blue-700"
>
{buildingTranslations[language].selectBuilding}
</Button>
</div>
const mainComponent = <div className="mb-6 p-4 border rounded-md bg-blue-50 shadow-sm relative">
{/* Header with title and change button */}
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-medium text-blue-800">
{buildingTranslations[language].selectedBuilding}
</h3>
<Button
onClick={() => router.push(`/panel/build`)}
className="bg-red-100 hover:bg-red-200 text-red-700 p-1 h-8 w-8 rounded-full flex items-center justify-center"
title={buildingTranslations[language].changeBuilding}
>
<X size={16} />
</Button>
</div>
{/* Building details grid */}
<div className="grid grid-cols-2 gap-2">
{/* UUID */}
<div>
<span className="font-semibold">UUID: </span>
{selectedBuilding.uu_id}
</div>
{/* Building Name */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildName}: </span>
{selectedBuilding.build_name}
</div>
{/* Building Number */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildNo}: </span>
{selectedBuilding.build_no}
</div>
{/* Address Code */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildAddressCode}: </span>
{selectedBuilding.gov_address_code}
</div>
{/* Max Floor */}
<div>
<span className="font-semibold">{buildingTranslations[language].buildMaxFloor}: </span>
{selectedBuilding.max_floor}
</div>
</div>
</div>
return (
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
{/* back to list button */}
<div className="flex justify-between items-center mb-6">
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
</div>
<h2 className="text-2xl font-bold mb-6">{translationsOfPage[language].title}</h2>
{/* ===== BUILDING SELECTION SECTION ===== */}
{selectedBuilding ? mainComponent : noSelectedBuildingComponent}
{/* ===== FORM SECTION ===== */}
{selectedBuilding && formComponent}
</div>
);
}
export const CreateFromBuildComponent = withCache(CreateFromComponentBase);
export default withCache(CreateFromBuildComponent);

View File

@ -0,0 +1,212 @@
import React, { useState, useEffect } from "react";
import { apiPostFetcher } from "@/lib/fetcher";
import { useRouter } from "next/navigation";
import { withCache } from "@/components/mutual/context/cache/withCache";
import { Button } from "@/components/mutual/ui/button";
import { Form } from "@/components/mutual/ui/form";
import { getCacheData, setCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { buildPartsSchema, updateEmptyValues } from "./schema";
import { buildPartsTranslations, translationsOfPage } from "./translations";
import { CheckBoxInput } from "@/components/mutual/formInputs/CheckBoxInput";
import { NumberInput } from "@/components/mutual/formInputs/NumberInput";
import { StringInput } from "@/components/mutual/formInputs/StringInput";
function UpdateFromComponentBase({
onlineData,
cacheData,
cacheLoading,
cacheError,
refreshCache,
updateCache,
clearCache,
activePageUrl
}: {
onlineData?: any;
cacheData?: { [url: string]: any } | null;
cacheLoading?: boolean;
cacheError?: string | null;
refreshCache?: (url?: string) => Promise<void>;
updateCache?: (url: string, data: any) => Promise<void>;
clearCache?: (url: string) => Promise<void>;
activePageUrl: string;
}) {
const language: LanguageTypes = onlineData?.lang || 'en';
const [cacheLoaded, setCacheLoaded] = useState<boolean>(false);
const [partUUID, setPartUUID] = useState<string>(""); const router = useRouter();
const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`;
const form = useForm({ resolver: zodResolver(buildPartsSchema), mode: "onSubmit", defaultValues: updateEmptyValues });
useEffect(() => {
const fetchData = async () => {
if (!cacheLoaded) {
try {
const cachedData = await getCacheData(activePageUrl);
if (cachedData) {
const formValues = form.getValues();
const mergedData = {
uu_id: "",
address_gov_code: cachedData.address_gov_code || formValues.address_gov_code || "",
part_no: cachedData.part_no ?? formValues.part_no ?? 0,
part_level: cachedData.part_level ?? formValues.part_level ?? 0,
part_code: cachedData.part_code || formValues.part_code || "",
part_gross_size: cachedData.part_gross_size ?? formValues.part_gross_size ?? 0,
part_net_size: cachedData.part_net_size ?? formValues.part_net_size ?? 0,
default_accessory: cachedData.default_accessory || formValues.default_accessory || "0",
human_livable: cachedData.human_livable ?? formValues.human_livable ?? true,
due_part_key: cachedData.due_part_key || formValues.due_part_key || "",
part_direction_uu_id: cachedData.part_direction_uu_id ?? formValues.part_direction_uu_id ?? null
};
form.reset(mergedData);
setPartUUID(cachedData.uu_id);
}
setCacheLoaded(true);
if (refreshCache) { refreshCache(activePageUrl); }
} catch (error) { console.error("Error fetching cache:", error); }
}
};
fetchData();
}, []);
const handleFieldBlur = async (fieldName: string, value: any) => {
if (value !== undefined) {
try {
const currentValues = form.getValues();
const updatedData = { ...currentValues, [fieldName]: value };
await setCacheData(activePageUrl, updatedData);
if (updateCache) { updateCache(activePageUrl, updatedData); }
} catch (error) { console.error("Error updating cache:", error); }
}
};
const onSubmit = async (data: any) => {
try {
console.log('Form data received:', data);
const formattedData = { ...data, uuid: partUUID };
const response = await apiPostFetcher<any>({
url: `/api/parts/update/${partUUID}`,
isNoCache: true,
body: formattedData,
});
await clearCacheData(activePageUrl);
if (clearCache) { clearCache(activePageUrl); }
if (response.success) { form.reset(updateEmptyValues); router.push(listUrl) }
} catch (error) { console.error("Error submitting form:", error); }
}
return (
<div className="w-full max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
{/* back to list button */}
<div className="flex justify-between items-center mb-6">
<Button onClick={() => router.push(listUrl)}>{translationsOfPage[language].back2List}</Button>
</div>
<h2 className="text-2xl font-bold mb-3">{translationsOfPage[language].title}</h2>
<h4 className="text-sm text-gray-500 mb-6">UUID: {partUUID}</h4>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
{/* Address Government Code */}
<StringInput
control={form.control}
name="address_gov_code"
label={buildPartsTranslations[language].address_gov_code}
placeholder={buildPartsTranslations[language].address_gov_code}
onBlurCallback={handleFieldBlur}
/>
{/* Part Number */}
<NumberInput
control={form.control}
name="part_no"
label={buildPartsTranslations[language].part_no}
placeholder={buildPartsTranslations[language].part_no}
onBlurCallback={handleFieldBlur}
/>
{/* Part Level */}
<NumberInput
control={form.control}
name="part_level"
label={buildPartsTranslations[language].part_level}
placeholder={buildPartsTranslations[language].part_level}
onBlurCallback={handleFieldBlur}
/>
{/* Part Code */}
<StringInput
control={form.control}
name="part_code"
label={buildPartsTranslations[language].part_code}
placeholder={buildPartsTranslations[language].part_code}
onBlurCallback={handleFieldBlur}
/>
{/* Part Gross Size */}
<NumberInput
control={form.control}
name="part_gross_size"
label={buildPartsTranslations[language].part_gross_size}
placeholder={buildPartsTranslations[language].part_gross_size}
onBlurCallback={handleFieldBlur}
/>
{/* Part Net Size */}
<NumberInput
control={form.control}
name="part_net_size"
label={buildPartsTranslations[language].part_net_size}
placeholder={buildPartsTranslations[language].part_net_size}
onBlurCallback={handleFieldBlur}
/>
{/* Default Accessory */}
<StringInput
control={form.control}
name="default_accessory"
label={buildPartsTranslations[language].default_accessory}
placeholder={buildPartsTranslations[language].default_accessory}
onBlurCallback={handleFieldBlur}
/>
{/* Human Livable */}
<CheckBoxInput
control={form.control}
name="human_livable"
label={buildPartsTranslations[language].human_livable}
onBlurCallback={handleFieldBlur}
/>
{/* Due Part Key */}
<StringInput
control={form.control}
name="due_part_key"
label={buildPartsTranslations[language].due_part_key}
placeholder={buildPartsTranslations[language].due_part_key}
onBlurCallback={handleFieldBlur}
/>
{/* Part Direction UUID */}
<StringInput
control={form.control}
name="part_direction_uu_id"
label={buildPartsTranslations[language].part_direction_uu_id}
placeholder={buildPartsTranslations[language].part_direction_uu_id}
onBlurCallback={handleFieldBlur}
/>
<Button type="submit" className="w-full">{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}</Button>
</form>
</Form>
</div>
);
}
export const UpdateFromComponent = withCache(UpdateFromComponentBase);
export default withCache(UpdateFromComponentBase);

View File

@ -0,0 +1,163 @@
'use client';
import React from "react";
import LoadingContent from "@/components/mutual/loader/component";
import { useRouter } from "next/navigation";
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, flexRender, ColumnDef } from "@tanstack/react-table";
import { Button } from "@/components/mutual/ui/button";
import { API_BASE_URL } from "@/config/config";
import { useTableData } from "@/hooks/useTableData";
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { DashboardPageProps } from "@/validations/mutual/table/validations";
import { PeopleSchemaInterface, listTranslations } from "../schema";
import DataTable from "./dataTable";
import TableForm from "./tableForm";
import MobilePaginationControls from "./mobilePaginationControls";
import TableHeader from "./tableHeader";
import { columnHelper, retrieveColumns } from "./columns";
import { setCacheData, getCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
const PeopleListPage: React.FC<DashboardPageProps> = React.memo((props) => {
const router = useRouter();
const activePageUrl = props.activePageUrl || '';
const language = props.onlineData?.lang as LanguageTypes || 'en';
const t = listTranslations[language];
const apiUrl = `${API_BASE_URL}/people/list`;
const [selectedPerson, setSelectedPerson] = React.useState<any>(null);
const [cacheLoaded, setCacheLoaded] = React.useState<boolean>(false);
const {
tableData, isLoading, error, form, pagination, apiPagination, setSorting, handleSortingChange, handleSelectChange,
handleFirstPage, handlePreviousPage, handleNextPage, getSortingIcon, handleFormSubmit,
isPreviousDisabled, isNextDisabled, pageSizeOptions, renderPageOptions, sorting, // Disabled states
} = useTableData({
apiUrl,
mapFormToRequestBody: (values) => {
const requestBody = {
page: values.page, size: values.size, orderField: values.orderField, orderType: values.orderType,
// query: selectedPerson ? { person_uu_id: selectedPerson.uu_id } : {}
}; return requestBody;
}
});
// Check for selected build in cache when component mounts
React.useEffect(() => {
const checkCacheForSelectedBuild = async () => {
if (!cacheLoaded) {
try {
const activePageUrl = props.activePageUrl || '';
const cachedData = await getCacheData(`${activePageUrl}/list`);
if (cachedData && cachedData.person && cachedData.person.uu_id) {
setSelectedPerson(cachedData.person.uu_id);
}
setCacheLoaded(true);
} catch (error) {
console.error("Error checking cache for selected build:", error);
setCacheLoaded(true);
}
}
};
checkCacheForSelectedBuild();
}, [props.activePageUrl]);
React.useEffect(() => {
const checkCacheForSelectedBuild = async () => {
try {
const cachedData = await getCacheData("/build/list");
if (cachedData && cachedData.build) { setSelectedPerson(cachedData.build) }
} catch (error) { console.error("Error checking cache for selected build:", error) }
};
checkCacheForSelectedBuild();
}, []);
// React.useEffect(() => {
// if (selectedPerson) { form.setValue("page", 1); form.setValue("query", { person_uu_id: selectedPerson.uu_id }) }
// }, [selectedPerson, form]);
const columnsArray = retrieveColumns(language, activePageUrl, router);
const columns = React.useMemo(() => columnsArray, [columnHelper]) as ColumnDef<PeopleSchemaInterface>[];
const table = useReactTable({
data: tableData,
columns,
state: { sorting, pagination },
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
manualPagination: true,
pageCount: apiPagination.totalPages || 1,
})
const dataTableComponent = <DataTable
table={table}
tableData={tableData}
isLoading={isLoading}
handleSortingChange={handleSortingChange}
getSortingIcon={getSortingIcon}
flexRender={flexRender}
t={t}
selected={selectedPerson}
router={router}
/>
const noSelectedBuildingComponent = <div className="p-8 text-center">
<div className="mb-4 text-gray-600">{t.noSelectedBuilding || "No building selected. Please select a building first."}</div>
<Button onClick={() => router.push(`/panel/build`)} className="bg-blue-600 text-white hover:bg-blue-700">
{t.selectBuilding || "Select Building"}
</Button>
</div>
return (
isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
<div className="flex justify-between items-center p-4">
<TableHeader
title={t.dataTable}
description={t.tableWithApiData}
isLoading={isLoading}
error={error}
t={t}
/>
<Button onClick={() => router.push(`/panel/${activePageUrl}/create`)} className="bg-primary text-white hover:bg-primary/90">
<span className="mr-2">{t.createNew}</span>
<span className="sr-only">{t.createNewPeople}</span>
</Button>
</div>
<TableForm
form={form}
handleFormSubmit={handleFormSubmit}
handleSelectChange={handleSelectChange}
renderPageOptions={renderPageOptions}
pageSizeOptions={pageSizeOptions}
apiPagination={apiPagination}
handleFirstPage={handleFirstPage}
handlePreviousPage={handlePreviousPage}
handleNextPage={handleNextPage}
isPreviousDisabled={isPreviousDisabled}
isNextDisabled={isNextDisabled}
t={t}
/>
{/* Mobile pagination controls - only visible on small screens */}
<MobilePaginationControls
handlePreviousPage={handlePreviousPage}
handleNextPage={handleNextPage}
isPreviousDisabled={isPreviousDisabled}
isNextDisabled={isNextDisabled}
t={t}
/>
{selectedPerson ? dataTableComponent : noSelectedBuildingComponent}
</div >
);
});
export default PeopleListPage;

View File

@ -0,0 +1,119 @@
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getPaginationRowModel,
flexRender,
createColumnHelper,
ColumnDef,
} from "@tanstack/react-table";
import { PeopleSchemaInterface } from "../schema";
import { peopleTranslations, translationsOfPage } from '../translations';
import { LanguageTypes } from "@/validations/mutual/language/validations";
import { Button } from "@/components/mutual/ui/button";
import { Pencil } from "lucide-react";
import { setCacheData } from "@/components/mutual/context/cache/context";
const columnHelper = createColumnHelper<PeopleSchemaInterface>();
function retrieveColumns(language: LanguageTypes, activePageUrl: string, router: any) {
const handleEditRow = async (row: PeopleSchemaInterface) => {
try {
await setCacheData(`${activePageUrl}/update`, row);
router.push(`/panel/${activePageUrl}/update`);
} catch (error) { console.error('Error storing row data in cache:', error) }
};
const actionButtonGroup = (info: any) => (
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.preventDefault();
console.log('Edit button clicked');
handleEditRow && handleEditRow(info.row.original);
}}
className="h-8 w-8 p-0"
>
<Pencil className="h-4 w-4" />
<span className="sr-only">Edit</span>
</Button>
);
const columnsArray = [
columnHelper.display({
id: 'actions',
header: () => <span>{translationsOfPage[language].actionButtonGroup}</span>,
cell: (info) => { return (actionButtonGroup(info)) }
}),
columnHelper.accessor('firstname', {
cell: info => info.getValue(),
header: () => <span>{peopleTranslations[language].firstname}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('surname', {
cell: info => String(info.getValue()),
header: () => <span>{peopleTranslations[language].surname}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('middle_name', {
cell: info => String(info.getValue()),
header: () => <span>{peopleTranslations[language].middle_name}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('sex_code', {
cell: info => info.getValue(),
header: () => <span>{peopleTranslations[language].sex_code}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('person_ref', {
cell: info => String(info.getValue()),
header: () => <span>{peopleTranslations[language].person_ref}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('person_tag', {
cell: info => String(info.getValue()),
header: () => <span>{peopleTranslations[language].person_tag}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('father_name', {
cell: info => info.getValue(),
header: () => <span>{peopleTranslations[language].father_name}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('mother_name', {
cell: info => info.getValue() ? 'Yes' : 'No',
header: () => <span>{peopleTranslations[language].mother_name}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('country_code', {
cell: info => info.getValue(),
header: () => <span>{peopleTranslations[language].country_code}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('national_identity_id', {
cell: info => info.getValue() || '',
header: () => <span>{peopleTranslations[language].national_identity_id}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('birth_place', {
cell: info => info.getValue() || '',
header: () => <span>{peopleTranslations[language].birth_place}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('birth_date', {
cell: info => info.getValue() || '',
header: () => <span>{peopleTranslations[language].birth_date}</span>,
footer: info => info.column.id
}),
columnHelper.accessor('tax_no', {
cell: info => info.getValue() || '',
header: () => <span>{peopleTranslations[language].tax_no}</span>,
footer: info => info.column.id
}),
]
return columnsArray;
}
export {
retrieveColumns,
columnHelper
}

View File

@ -0,0 +1,129 @@
'use client';
import React from "react";
import { Button } from "@/components/mutual/ui/button";
import { X } from "lucide-react";
import { useReactTable } from "@tanstack/react-table";
import { flexRender } from "@tanstack/react-table";
import { PeopleSchemaInterface } from "../schema";
import { Translations } from "./types";
interface DataTableProps {
table: ReturnType<typeof useReactTable>;
tableData: PeopleSchemaInterface[];
isLoading: boolean;
handleSortingChange: (columnId: string) => void;
getSortingIcon: (columnId: string) => React.ReactNode;
flexRender: typeof flexRender;
t: Translations;
selected: any | null;
router: any;
}
const DataTable: React.FC<DataTableProps> = React.memo(({
table,
tableData,
isLoading,
handleSortingChange,
getSortingIcon,
flexRender,
t,
selected,
router,
}) => {
return (
<div className="overflow-x-auto relative">
{/* Semi-transparent loading overlay that preserves interactivity */}
{isLoading && (
<div className="absolute inset-0 bg-white bg-opacity-60 flex items-center justify-center z-10">
{/* We don't put anything here as we already have the loading indicator in the header */}
</div>
)}
{selected && (
<div className="mb-4 p-4 border rounded-md bg-blue-50 shadow-sm relative">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-medium text-blue-800">{t.selectedPerson || "Selected Person"}</h3>
<Button
onClick={() => router.push(`/panel/people`)}
className="bg-red-100 hover:bg-red-200 text-red-700 p-1 h-8 w-8 rounded-full flex items-center justify-center"
title={t.changePerson || "Change Person"}
>
<X size={16} />
</Button>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<span className="font-semibold">ID: </span>
{selected.national_identity_id || '-'}
</div>
<div>
<span className="font-semibold">{t.name || "Name"}: </span>
{selected.firstname || '-'}
</div>
<div>
<span className="font-semibold">{t.surname || "Surname"}: </span>
{selected.surname || '-'}
</div>
<div>
<span className="font-semibold">{t.birthDate || "Birth Date"}: </span>
{selected.birth_date || '-'}
</div>
<div>
<span className="font-semibold">{t.taxNo || "Tax No"}: </span>
{selected.tax_no || '-'}
</div>
</div>
</div>
)}
<table className="min-w-full divide-y divide-gray-200">
<caption className="sr-only">{t.dataTable}</caption>
<thead className="bg-gray-50">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={() => handleSortingChange(header.column.id)}
aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined}
scope="col"
>
<div className="flex items-center space-x-1">
{flexRender(header.column.columnDef.header, header.getContext())}
<span>{getSortingIcon(header.column.id)}</span>
</div>
</th>
);
})}
</tr>
))}
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{tableData.length === 0 && !isLoading ? (
<tr>
<td colSpan={table.getAllColumns().length} className="px-6 py-4 text-center text-gray-500">
{t.noDataAvailable}
</td>
</tr>
) : (
table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-gray-50">
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
);
})}
</tr>
))
)}
</tbody>
</table>
</div>
);
});
export default DataTable;

View File

@ -0,0 +1,34 @@
import { MobilePaginationControlsProps } from "@/validations/mutual/table/validations";
import { Button } from "@/components/mutual/ui/button";
const MobilePaginationControls: React.FC<MobilePaginationControlsProps> = ({
handlePreviousPage,
handleNextPage,
isPreviousDisabled,
isNextDisabled,
t
}) => {
return (
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:hidden">
<div className="flex-1 flex justify-between">
<Button
onClick={handlePreviousPage}
disabled={isPreviousDisabled()}
variant="outline"
size="sm"
aria-label="Go to previous page"
>{t.previous}</Button>
<Button
onClick={handleNextPage}
disabled={isNextDisabled()}
variant="outline"
size="sm"
className="ml-2"
aria-label="Go to next page"
>{t.next}</Button>
</div>
</div>
);
};
export default MobilePaginationControls;

View File

@ -0,0 +1,152 @@
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from "@/components/mutual/ui/form";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select";
import { UseFormReturn } from "react-hook-form";
import { Translations } from "./types";
import { Button } from "@/components/mutual/ui/button";
interface TableFormProps {
form: UseFormReturn<any>;
handleFormSubmit: (e: React.FormEvent) => void;
handleSelectChange: (value: string, field: { onChange: (value: number) => void }) => void;
renderPageOptions: () => { key: string | number; value: string; label: string }[];
pageSizeOptions: number[];
apiPagination: {
size: number;
page: number;
totalCount: number;
totalPages: number;
pageCount: number;
};
handleFirstPage: () => void;
handlePreviousPage: () => void;
handleNextPage: () => void;
isPreviousDisabled: () => boolean;
isNextDisabled: () => boolean;
t: Translations;
}
const TableForm: React.FC<TableFormProps> = ({
form,
handleFormSubmit,
handleSelectChange,
renderPageOptions,
pageSizeOptions,
apiPagination,
handleFirstPage,
handlePreviousPage,
handleNextPage,
isPreviousDisabled,
isNextDisabled,
t
}) => {
return (
<div className="p-4 border-b border-gray-200">
<Form {...form}>
<form onSubmit={handleFormSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="md:col-span-1">
<FormField
control={form.control}
name="page"
render={({ field }) => (
<FormItem>
<FormLabel>{t.page}</FormLabel>
<Select
value={field.value.toString()}
onValueChange={(value: string) => handleSelectChange(value, field)}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t.selectPage} />
</SelectTrigger>
</FormControl>
<SelectContent>
{renderPageOptions().length > 0 ? (
renderPageOptions().map((option: { key: string | number; value: string; label: string }) => (
<SelectItem key={option.key} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<SelectItem key="1" value="1">1</SelectItem>
)}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="md:col-span-1">
<FormField
control={form.control}
name="size"
render={({ field }) => (
<FormItem>
<FormLabel>{t.size}</FormLabel>
<Select
value={field.value.toString()}
onValueChange={(value: string) => handleSelectChange(value, field)}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t.selectSize} />
</SelectTrigger>
</FormControl>
<SelectContent>
{pageSizeOptions.map((size: number) => (
<SelectItem key={size} value={size.toString()}>
{size}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="md:col-span-1 flex items-end">
<p className="text-sm text-gray-700">
{t.page}: <span className="font-medium">{apiPagination.page}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.totalPages}</span> ·
{t.size}: <span className="font-medium">{apiPagination.pageCount}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.size}</span> ·
{t.total}: <span className="font-medium">{apiPagination.totalCount}</span> {t.items}
</p>
</div>
<div className="md:col-span-1 flex items-end justify-end space-x-2">
<Button
onClick={handleFirstPage}
disabled={isPreviousDisabled()}
variant="outline"
size="sm"
aria-label="Go to first page"
>{t.first}</Button>
<Button
onClick={handlePreviousPage}
disabled={isPreviousDisabled()}
variant="outline"
size="sm"
aria-label="Go to previous page"
>{t.previous}</Button>
<Button
onClick={handleNextPage}
disabled={isNextDisabled()}
variant="outline"
size="sm"
aria-label="Go to next page"
>{t.next}</Button>
{/* <Button type="submit" disabled={isLoading} size="sm">Fetch</Button> */}
</div>
</form>
</Form>
</div>
);
};
export default TableForm;

View File

@ -0,0 +1,24 @@
import { TableHeaderProps, ErrorDisplayProps } from "@/validations/mutual/table/validations";
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
return <div className="text-red-500">{message}</div>;
};
const TableHeader: React.FC<TableHeaderProps> = ({ title, description, isLoading, error, t }) => {
return (
<div className="p-4 border-b border-gray-200">
<div className="flex justify-between items-center">
<div>
<h2 className="text-lg font-semibold text-gray-800">{title}</h2>
<p className="text-sm text-gray-500">{description}</p>
</div>
{/* {isLoading && <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />} */}
{error && <ErrorDisplay message={error} />}
</div>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,24 @@
import { Translations as BaseTranslations } from "@/validations/mutual/table/validations";
// Extended Translations interface to include person-related translations
interface Translations extends BaseTranslations {
// Building-related translations (for backward compatibility)
selectedBuilding?: string;
buildName?: string;
buildNo?: string;
buildAddressCode?: string;
buildMaxFloor?: string;
noSelectedBuilding?: string;
selectBuilding?: string;
changeBuilding?: string;
// Person-related translations
selectedPerson: string;
name: string;
surname: string;
birthDate: string;
taxNo: string;
changePerson: string;
}
export type { Translations };

View File

@ -0,0 +1,151 @@
import { LanguageTypes } from "@/validations/mutual/language/validations";
import * as z from "zod";
/**
* Zod schema for People
* Corresponds to the People class in Python
* People that are related to users in the application
*/
const peopleSchema = z.object({
uu_id: z.string().describe("UUID of the person"),
firstname: z.string().describe("First name of the person"),
surname: z.string().max(24).describe("Surname of the person"),
middle_name: z.string().default("").describe("Middle name of the person"),
sex_code: z.string().max(1).describe("Sex code of the person (e.g., M/F)"),
person_ref: z.string().default("").describe("Reference ID for the person"),
person_tag: z.string().default("").describe("Unique tag for the person"),
father_name: z.string().default("").describe("Father's name of the person"),
mother_name: z.string().default("").describe("Mother's name of the person"),
country_code: z
.string()
.max(4)
.default("TR")
.describe("Country code of the person"),
national_identity_id: z
.string()
.default("")
.describe("National identity ID of the person"),
birth_place: z.string().default("").describe("Birth place of the person"),
birth_date: z
.string()
.datetime()
.default("1900-01-01")
.describe("Birth date of the person"),
tax_no: z.string().default("").describe("Tax number of the person"),
});
export type People = z.infer<typeof peopleSchema>;
interface PeopleSchemaInterface {
firstname: string;
surname: string;
middle_name: string;
sex_code: string;
person_ref: string;
person_tag: string;
father_name: string;
mother_name: string;
country_code: string;
national_identity_id: string;
birth_place: string;
birth_date: string;
tax_no: string;
}
const createEmptyValues: PeopleSchemaInterface = {
firstname: "",
surname: "",
middle_name: "",
sex_code: "",
person_ref: "",
person_tag: "",
father_name: "",
mother_name: "",
country_code: "",
national_identity_id: "",
birth_place: "",
birth_date: "",
tax_no: "",
};
const updateEmptyValues: PeopleSchemaInterface = {
firstname: "",
surname: "",
middle_name: "",
sex_code: "",
person_ref: "",
person_tag: "",
father_name: "",
mother_name: "",
country_code: "",
national_identity_id: "",
birth_place: "",
birth_date: "",
tax_no: "",
};
const listTranslations: Record<LanguageTypes, any> = {
en: {
createNew: "Create New Person",
createNewPeople: "Create New Person",
dataTable: "People Table",
tableWithApiData: "Table with API Data",
loading: "Loading...",
noDataAvailable: "No data available",
page: "Page",
size: "Size",
total: "Total",
items: "items",
first: "First",
previous: "Previous",
next: "Next",
selectPage: "Select page",
selectSize: "Select size",
actionButtonGroup: "Actions",
selectedPerson: "Selected Person",
name: "Name",
surname: "Surname",
birthDate: "Birth Date",
taxNo: "Tax No",
changePerson: "Change Person",
// Keep these for backward compatibility
selectedBuilding: "Selected Person",
buildName: "Name",
buildNo: "ID",
buildAddressCode: "Address",
buildMaxFloor: "Birth Date",
},
tr: {
createNew: "Yeni Kişi Oluştur",
createNewPeople: "Yeni Kişi Oluştur",
dataTable: "Kişiler Tablosu",
tableWithApiData: "API Verili Tablo",
loading: "Yükleniyor...",
noDataAvailable: "Veri bulunamadı",
page: "Sayfa",
size: "Boyut",
total: "Toplam",
items: "öğe",
first: "İlk",
previous: "Önceki",
next: "Sonraki",
selectPage: "Sayfa seç",
selectSize: "Boyut seç",
actionButtonGroup: "Eylemler",
selectedPerson: "Seçilen Kişi",
name: "Ad",
surname: "Soyad",
birthDate: "Doğum Tarihi",
taxNo: "Vergi No",
changePerson: "Kişiyi Değiştir",
// Keep these for backward compatibility
selectedBuilding: "Seçilen Kişi",
buildName: "Ad",
buildNo: "Kimlik No",
buildAddressCode: "Adres",
buildMaxFloor: "Doğum Tarihi",
},
};
export type { PeopleSchemaInterface };
export { peopleSchema, createEmptyValues, updateEmptyValues, listTranslations };

View File

@ -0,0 +1,85 @@
interface PeopleSchemaInterface {
firstname: string;
surname: string;
middle_name: string;
sex_code: string;
person_ref: string;
person_tag: string;
father_name: string;
mother_name: string;
country_code: string;
national_identity_id: string;
birth_place: string;
birth_date: string;
tax_no: string;
}
const peopleTranslations = {
tr: {
firstname: "Ad",
surname: "Soyad",
middle_name: "Ortadaki Ad",
sex_code: "Cinsiyet",
person_ref: "Kişi Referans",
person_tag: "Kişi Etiket",
father_name: "Baba Adı",
mother_name: "Anne Adı",
country_code: "Ülke Kodu",
national_identity_id: "Kimlik No",
birth_place: "Doğum Yeri",
birth_date: "Doğum Tarihi",
tax_no: "Vergi No",
},
en: {
firstname: "First Name",
surname: "Surname",
middle_name: "Middle Name",
sex_code: "Sex Code",
person_ref: "Person Ref",
person_tag: "Person Tag",
father_name: "Father Name",
mother_name: "Mother Name",
country_code: "Country Code",
national_identity_id: "National Identity ID",
birth_place: "Birth Place",
birth_date: "Birth Date",
tax_no: "Tax No",
},
};
const translationsOfPage = {
tr: {
title: "Kişi Oluştur",
updateTitle: "Kişiyi Güncelle",
back2List: "Listeye Geri Dön",
actionButtonGroup: "İşlemler",
selectedPerson: "Seçilen Kişi",
name: "Ad",
surname: "Soyad",
birthDate: "Doğum Tarihi",
taxNo: "Vergi No",
changePerson: "Kişiyi Değiştir",
createNew: "Yeni Kişi Oluştur",
createNewPeople: "Yeni Kişi Oluştur",
dataTable: "Kişiler Tablosu",
noDataAvailable: "Veri bulunamadı"
},
en: {
title: "Create Person",
updateTitle: "Update Person",
back2List: "Back to List",
actionButtonGroup: "Actions",
selectedPerson: "Selected Person",
name: "Name",
surname: "Surname",
birthDate: "Birth Date",
taxNo: "Tax No",
changePerson: "Change Person",
createNew: "Create New Person",
createNewPeople: "Create New Person",
dataTable: "People Table",
noDataAvailable: "No data available"
},
};
export { peopleTranslations, translationsOfPage };