updated application pages build tested
This commit is contained in:
parent
a9655c5f48
commit
c259ad3d99
|
|
@ -60,6 +60,7 @@ SuperPartsDeleteEvent = Event(
|
|||
def super_parts_list_callable(list_options: PaginateOnly, headers: CommonHeaders):
|
||||
list_options = PaginateOnly(**list_options.model_dump())
|
||||
# TODO: Pydantic Model must be implemnted for list_options.query
|
||||
print('list_options', list_options.model_dump())
|
||||
with Build.new_session() as db_session:
|
||||
BuildParts.set_session(db_session)
|
||||
base_query = BuildParts.query.filter()
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ export interface UseTableDataProps {
|
|||
mapFormToRequestBody?: (values: TableFormValues) => any;
|
||||
customFormSchema?: z.ZodType<any, any>;
|
||||
customFormValues?: Record<string, any>;
|
||||
baseQuery?: Record<string, any>; // Base query that will be merged with user query
|
||||
pageField?: keyof TableFormValues;
|
||||
sizeField?: keyof TableFormValues;
|
||||
orderFieldField?: keyof TableFormValues;
|
||||
|
|
@ -77,6 +78,7 @@ export function useTableData({
|
|||
mapFormToRequestBody,
|
||||
customFormSchema = defaultFormSchema,
|
||||
customFormValues = defaultFormValues,
|
||||
baseQuery = {}, // Default empty base query
|
||||
pageField = "page" as keyof TableFormValues,
|
||||
sizeField = "size" as keyof TableFormValues,
|
||||
orderFieldField = "orderField" as keyof TableFormValues,
|
||||
|
|
@ -161,6 +163,15 @@ export function useTableData({
|
|||
query: values[queryField],
|
||||
};
|
||||
|
||||
// Make sure query is not empty if baseQuery has values
|
||||
if (Object.keys(baseQuery).length > 0) {
|
||||
if (!requestBody.query || typeof requestBody.query !== "object") {
|
||||
requestBody.query = {};
|
||||
}
|
||||
// Add baseQuery properties to the query object
|
||||
Object.assign(requestBody.query, baseQuery);
|
||||
}
|
||||
|
||||
const response = await apiPostFetcher<any>({
|
||||
url: apiUrl,
|
||||
isNoCache: true,
|
||||
|
|
|
|||
|
|
@ -6,31 +6,18 @@ 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, BuildPartsCreateInterface, buildPartsSchema } from "./schema";
|
||||
import { buildPartsTranslations, translationsOfPage } from "./translations";
|
||||
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";
|
||||
|
||||
const emptyValues: 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,
|
||||
};
|
||||
|
||||
function CreateFromComponentBase({
|
||||
onlineData,
|
||||
cacheData,
|
||||
|
|
@ -53,17 +40,29 @@ function CreateFromComponentBase({
|
|||
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: emptyValues
|
||||
});
|
||||
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 {
|
||||
console.log("Directly fetching cache for URL:", activePageUrl);
|
||||
const cachedData = await getCacheData(activePageUrl);
|
||||
if (cachedData) {
|
||||
const formValues = form.getValues();
|
||||
|
|
@ -102,25 +101,30 @@ function CreateFromComponentBase({
|
|||
};
|
||||
|
||||
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(emptyValues); router.push(listUrl) }
|
||||
} catch (error) { console.error("Error submitting form:", error) }
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
const formComponent = <>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
{/* ----- Basic Information ----- */}
|
||||
{/* Address Government Code */}
|
||||
<StringInput
|
||||
control={form.control}
|
||||
|
|
@ -130,6 +134,7 @@ function CreateFromComponentBase({
|
|||
onBlurCallback={handleFieldBlur}
|
||||
/>
|
||||
|
||||
{/* ----- Part Identification ----- */}
|
||||
{/* Part Number */}
|
||||
<NumberInput
|
||||
control={form.control}
|
||||
|
|
@ -157,6 +162,7 @@ function CreateFromComponentBase({
|
|||
onBlurCallback={handleFieldBlur}
|
||||
/>
|
||||
|
||||
{/* ----- Size Information ----- */}
|
||||
{/* Part Gross Size */}
|
||||
<NumberInput
|
||||
control={form.control}
|
||||
|
|
@ -175,6 +181,7 @@ function CreateFromComponentBase({
|
|||
onBlurCallback={handleFieldBlur}
|
||||
/>
|
||||
|
||||
{/* ----- Additional Properties ----- */}
|
||||
{/* Default Accessory */}
|
||||
<StringInput
|
||||
control={form.control}
|
||||
|
|
@ -210,9 +217,86 @@ function CreateFromComponentBase({
|
|||
onBlurCallback={handleFieldBlur}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full">{language === 'en' ? translationsOfPage.en.title : translationsOfPage.tr.title}</Button>
|
||||
{/* ----- 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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,542 +0,0 @@
|
|||
'use client';
|
||||
import React from "react";
|
||||
import {
|
||||
useReactTable,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
getPaginationRowModel,
|
||||
flexRender,
|
||||
createColumnHelper,
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import { buildPartsTranslations } from './translations';
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from "@/components/mutual/ui/form";
|
||||
import { Button } from "@/components/mutual/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select";
|
||||
import { API_BASE_URL } from "@/config/config";
|
||||
import { useTableData } from "@/hooks/useTableData";
|
||||
import { LanguageTypes } from "@/validations/mutual/language/validations";
|
||||
import {
|
||||
Translations,
|
||||
TableHeaderProps,
|
||||
LoadingSpinnerProps,
|
||||
ErrorDisplayProps,
|
||||
MobilePaginationControlsProps,
|
||||
DashboardPageProps,
|
||||
} from "@/validations/mutual/table/validations";
|
||||
import LoadingContent from "@/components/mutual/loader/component";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { setCacheData } from "@/components/mutual/context/cache/context";
|
||||
import { BuildPartsSchemaInterface } from "./schema";
|
||||
|
||||
interface DataTableProps {
|
||||
table: ReturnType<typeof useReactTable>;
|
||||
tableData: BuildPartsSchemaInterface[];
|
||||
isLoading: boolean;
|
||||
handleSortingChange: (columnId: string) => void;
|
||||
getSortingIcon: (columnId: string) => React.ReactNode;
|
||||
flexRender: typeof flexRender;
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
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 translations: Record<LanguageTypes, Translations> = {
|
||||
en: {
|
||||
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',
|
||||
},
|
||||
tr: {
|
||||
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',
|
||||
}
|
||||
};
|
||||
|
||||
const DataTable: React.FC<DataTableProps> = React.memo(({
|
||||
table,
|
||||
tableData,
|
||||
isLoading,
|
||||
handleSortingChange,
|
||||
getSortingIcon,
|
||||
flexRender,
|
||||
t,
|
||||
}) => {
|
||||
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>
|
||||
)}
|
||||
<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>
|
||||
);
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
|
||||
return <div className="text-red-500">{message}</div>;
|
||||
};
|
||||
|
||||
const BuildListPage: React.FC<DashboardPageProps> = React.memo((props) => {
|
||||
// Initialize translation with English as default
|
||||
const language = props.onlineData?.lang as LanguageTypes || 'en';
|
||||
const t = translations[language];
|
||||
const searchParams = props.searchParams;
|
||||
console.log('searchParams', searchParams);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
form,
|
||||
tableData,
|
||||
sorting,
|
||||
isLoading,
|
||||
error,
|
||||
pagination,
|
||||
apiPagination,
|
||||
setSorting,
|
||||
handleSortingChange,
|
||||
handleSelectChange,
|
||||
handlePageChange,
|
||||
handleFirstPage,
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
getSortingIcon,
|
||||
handleFormSubmit,
|
||||
// Disabled states
|
||||
isPreviousDisabled,
|
||||
isNextDisabled,
|
||||
pageSizeOptions,
|
||||
renderPageOptions,
|
||||
} = useTableData({ apiUrl: `${API_BASE_URL}/parts/list` });
|
||||
|
||||
const activePageUrl = props.activePageUrl || '';
|
||||
const handleEditRow = async (row: BuildPartsSchemaInterface) => {
|
||||
try {
|
||||
// Store the row data in the cache
|
||||
await setCacheData(`${activePageUrl}/update`, row);
|
||||
console.log('Row data stored in cache:', row);
|
||||
|
||||
// Navigate to the update form
|
||||
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 columnHelper = createColumnHelper<BuildPartsSchemaInterface>();
|
||||
const columns = React.useMemo(() => [
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
header: () => <span>{t.actionButtonGroup}</span>,
|
||||
cell: (info) => { return (actionButtonGroup(info)) }
|
||||
}),
|
||||
// columnHelper.accessor('uu_id', {
|
||||
// cell: info => info.getValue(),
|
||||
// header: () => <span>{buildPartsTranslations[language].uu_id}</span>,
|
||||
// footer: info => info.column.id
|
||||
// }),
|
||||
// columnHelper.accessor('build_uu_id', {
|
||||
// cell: info => info.getValue(),
|
||||
// header: () => <span>{buildPartsTranslations[language].build_uu_id}</span>,
|
||||
// footer: info => info.column.id
|
||||
// }),
|
||||
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
|
||||
}),
|
||||
], [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,
|
||||
})
|
||||
|
||||
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">Create New</span>
|
||||
<span className="sr-only">Create new item</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}
|
||||
/>
|
||||
|
||||
<DataTable
|
||||
table={table}
|
||||
tableData={tableData}
|
||||
isLoading={isLoading}
|
||||
handleSortingChange={handleSortingChange}
|
||||
getSortingIcon={getSortingIcon}
|
||||
flexRender={flexRender}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default BuildListPage;
|
||||
|
|
@ -11,28 +11,13 @@ import { useForm } from "react-hook-form";
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { LanguageTypes } from "@/validations/mutual/language/validations";
|
||||
|
||||
import { buildPartsSchema, BuildPartsInterface } from "./schema";
|
||||
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";
|
||||
|
||||
const emptyValues: 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
|
||||
};
|
||||
|
||||
function UpdateFromComponentBase({
|
||||
onlineData,
|
||||
cacheData,
|
||||
|
|
@ -57,11 +42,7 @@ function UpdateFromComponentBase({
|
|||
const [partUUID, setPartUUID] = useState<string>(""); const router = useRouter();
|
||||
const listUrl = `/panel/${activePageUrl?.replace("/update", "")}`;
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(buildPartsSchema),
|
||||
mode: "onSubmit",
|
||||
defaultValues: emptyValues
|
||||
});
|
||||
const form = useForm({ resolver: zodResolver(buildPartsSchema), mode: "onSubmit", defaultValues: updateEmptyValues });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
|
|
@ -108,10 +89,7 @@ function UpdateFromComponentBase({
|
|||
const onSubmit = async (data: any) => {
|
||||
try {
|
||||
console.log('Form data received:', data);
|
||||
const formattedData = {
|
||||
...data,
|
||||
uuid: partUUID
|
||||
};
|
||||
const formattedData = { ...data, uuid: partUUID };
|
||||
const response = await apiPostFetcher<any>({
|
||||
url: `/api/parts/update/${partUUID}`,
|
||||
isNoCache: true,
|
||||
|
|
@ -119,7 +97,7 @@ function UpdateFromComponentBase({
|
|||
});
|
||||
await clearCacheData(activePageUrl);
|
||||
if (clearCache) { clearCache(activePageUrl); }
|
||||
if (response.success) { form.reset(emptyValues); router.push(listUrl) }
|
||||
if (response.success) { form.reset(updateEmptyValues); router.push(listUrl) }
|
||||
} catch (error) { console.error("Error submitting form:", error); }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 BuildListPage: 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 BuildListPage;
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 };
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { LanguageTypes } from "@/validations/mutual/language/validations";
|
||||
import * as z from "zod";
|
||||
|
||||
/**
|
||||
|
|
@ -48,9 +49,99 @@ interface BuildPartsSchemaInterface {
|
|||
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 };
|
||||
export {
|
||||
buildPartsSchema,
|
||||
buildPartsSchemaCreate,
|
||||
createEmptyValues,
|
||||
updateEmptyValues,
|
||||
listTranslations,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export const buildPartsTranslations = {
|
||||
const buildPartsTranslations = {
|
||||
tr: {
|
||||
uu_id: "UUID",
|
||||
build_uu_id: "Bina UUID",
|
||||
|
|
@ -29,17 +29,42 @@ export const buildPartsTranslations = {
|
|||
},
|
||||
};
|
||||
|
||||
export const translationsOfPage = {
|
||||
const translationsOfPage = {
|
||||
tr: {
|
||||
title: "Bina Bölümü Oluştur",
|
||||
updateTitle: "Bina Bölümü Güncelle",
|
||||
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",
|
||||
},
|
||||
};
|
||||
|
||||
export default buildPartsTranslations;
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -25,17 +25,22 @@ import { API_BASE_URL } from "@/config/config";
|
|||
import { useTableData } from "@/hooks/useTableData";
|
||||
import { LanguageTypes } from "@/validations/mutual/language/validations";
|
||||
import {
|
||||
Translations,
|
||||
Translations as BaseTranslations,
|
||||
TableHeaderProps,
|
||||
LoadingSpinnerProps,
|
||||
ErrorDisplayProps,
|
||||
MobilePaginationControlsProps,
|
||||
DashboardPageProps,
|
||||
} from "@/validations/mutual/table/validations";
|
||||
|
||||
// Extended Translations interface with selectedBuilding property
|
||||
interface Translations extends BaseTranslations {
|
||||
selectedBuilding: string;
|
||||
}
|
||||
import LoadingContent from "@/components/mutual/loader/component";
|
||||
import { Pencil } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { setCacheData } from "@/components/mutual/context/cache/context";
|
||||
import { setCacheData, getCacheData, clearCacheData } from "@/components/mutual/context/cache/context";
|
||||
import { BuildSchemaInterface } from "./schema";
|
||||
|
||||
interface DataTableProps {
|
||||
|
|
@ -45,7 +50,11 @@ interface DataTableProps {
|
|||
handleSortingChange: (columnId: string) => void;
|
||||
getSortingIcon: (columnId: string) => React.ReactNode;
|
||||
flexRender: typeof flexRender;
|
||||
t: Translations;
|
||||
t: any;
|
||||
selected: string | null;
|
||||
setSelected: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
activePageUrl: string;
|
||||
router: ReturnType<typeof useRouter>;
|
||||
}
|
||||
|
||||
interface TableFormProps {
|
||||
|
|
@ -69,7 +78,7 @@ interface TableFormProps {
|
|||
t: Translations;
|
||||
}
|
||||
|
||||
const translations: Record<LanguageTypes, Translations> = {
|
||||
const translations: Record<LanguageTypes, any> = {
|
||||
en: {
|
||||
dataTable: 'Data Table',
|
||||
tableWithApiData: 'Table with API Data',
|
||||
|
|
@ -85,6 +94,16 @@ const translations: Record<LanguageTypes, Translations> = {
|
|||
selectPage: 'Select page',
|
||||
selectSize: 'Select size',
|
||||
actionButtonGroup: 'Actions',
|
||||
selectedBuilding: 'Selected Building',
|
||||
buildParts: 'Create Parts',
|
||||
areas: 'Create Areas',
|
||||
buildNo: 'Building No',
|
||||
buildName: 'Building Name',
|
||||
buildAddress: 'Building Address',
|
||||
buildAddressCode: 'Building Address Code',
|
||||
buildMaxFloor: 'Building Max Floor',
|
||||
clearSelection: 'Clear Selection',
|
||||
// lands: 'Create Lands',
|
||||
},
|
||||
tr: {
|
||||
dataTable: 'Veri Tablosu',
|
||||
|
|
@ -101,6 +120,16 @@ const translations: Record<LanguageTypes, Translations> = {
|
|||
selectPage: 'Sayfa seç',
|
||||
selectSize: 'Boyut seç',
|
||||
actionButtonGroup: 'Eylemler',
|
||||
selectedBuilding: 'Seçilen Bina',
|
||||
buildParts: 'Daire Oluştur',
|
||||
areas: 'Alanlar Oluştur',
|
||||
buildNo: 'Bina No',
|
||||
buildName: 'Bina Adı',
|
||||
buildAddress: 'Bina Adresi',
|
||||
buildAddressCode: 'Bina Adres Kodu',
|
||||
buildMaxFloor: 'Bina Max Kat',
|
||||
clearSelection: 'Seçimi Temizle',
|
||||
// lands: 'Alanlar Oluştur',
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -112,7 +141,32 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
|||
getSortingIcon,
|
||||
flexRender,
|
||||
t,
|
||||
selected,
|
||||
setSelected,
|
||||
activePageUrl,
|
||||
router,
|
||||
}) => {
|
||||
|
||||
const handleRowClick = (rowData: BuildSchemaInterface) => {
|
||||
setSelected(rowData.uu_id);
|
||||
const selectedData = {
|
||||
build: rowData,
|
||||
// build_owner: null // This could be populated if you have owner data
|
||||
};
|
||||
setCacheData(`${activePageUrl}/list`, selectedData);
|
||||
};
|
||||
|
||||
const handleClearSelection = async () => {
|
||||
setSelected(null);
|
||||
try {
|
||||
// Use clearCacheData to properly remove the cache entry
|
||||
await clearCacheData(`${activePageUrl}/list`);
|
||||
console.log("Cache cleared successfully");
|
||||
} catch (error) {
|
||||
console.error("Error clearing selection cache:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto relative">
|
||||
{/* Semi-transparent loading overlay that preserves interactivity */}
|
||||
|
|
@ -121,8 +175,64 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
|||
{/* 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">
|
||||
<h3 className="text-lg font-medium text-blue-800 mb-2">{t.selectedBuilding}</h3>
|
||||
<div className="flex">
|
||||
{/* 2/3 section for building details */}
|
||||
<div className="w-2/3 pr-4">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<span className="font-semibold">UUID: </span>
|
||||
{tableData.find(row => row.uu_id === selected)?.uu_id}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">{t.buildName}: </span>
|
||||
{tableData.find(row => row.uu_id === selected)?.build_name}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">{t.buildNo}: </span>
|
||||
{tableData.find(row => row.uu_id === selected)?.build_no}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">{t.buildAddressCode}: </span>
|
||||
{tableData.find(row => row.uu_id === selected)?.gov_address_code}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">{t.buildMaxFloor}: </span>
|
||||
{tableData.find(row => row.uu_id === selected)?.max_floor}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 1/3 section for action buttons */}
|
||||
<div className="w-1/3 border-l pl-4 flex flex-col justify-center space-y-2">
|
||||
<Button
|
||||
onClick={() => router.push(`/panel/build/parts/create`)}
|
||||
className="bg-green-600 text-white hover:bg-green-700 w-full"
|
||||
>
|
||||
{t.buildParts}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => router.push(`/panel/build/area/create`)}
|
||||
className="bg-blue-600 text-white hover:bg-blue-700 w-full"
|
||||
>
|
||||
{t.areas}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClearSelection}
|
||||
className="bg-red-600 text-white hover:bg-red-700 w-full"
|
||||
>
|
||||
{t.clearSelection}
|
||||
</Button>
|
||||
</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}>
|
||||
|
|
@ -154,7 +264,11 @@ const DataTable: React.FC<DataTableProps> = React.memo(({
|
|||
</tr>
|
||||
) : (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className="hover:bg-gray-50">
|
||||
<tr
|
||||
key={row.id}
|
||||
className={`hover:bg-gray-50 cursor-pointer ${selected === (row.original as BuildSchemaInterface).uu_id ? 'bg-blue-100' : ''}`}
|
||||
onClick={() => handleRowClick(row.original as BuildSchemaInterface)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
return (
|
||||
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
|
|
@ -343,10 +457,31 @@ const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
|
|||
const BuildListPage: React.FC<DashboardPageProps> = React.memo((props) => {
|
||||
// Initialize translation with English as default
|
||||
const language = props.onlineData?.lang as LanguageTypes || 'en';
|
||||
|
||||
const [selected, setSelected] = React.useState<string | null>(null);
|
||||
const [cacheLoaded, setCacheLoaded] = React.useState<boolean>(false);
|
||||
const t = translations[language];
|
||||
const router = useRouter();
|
||||
|
||||
// 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.build && cachedData.build.uu_id) {
|
||||
setSelected(cachedData.build.uu_id);
|
||||
}
|
||||
setCacheLoaded(true);
|
||||
} catch (error) {
|
||||
console.error("Error checking cache for selected build:", error);
|
||||
setCacheLoaded(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
checkCacheForSelectedBuild();
|
||||
}, [props.activePageUrl]);
|
||||
|
||||
const {
|
||||
form,
|
||||
tableData,
|
||||
|
|
@ -373,6 +508,7 @@ const BuildListPage: React.FC<DashboardPageProps> = React.memo((props) => {
|
|||
const activePageUrl = props.activePageUrl || '';
|
||||
const handleEditRow = async (row: BuildSchemaInterface) => {
|
||||
try {
|
||||
setSelected(row.uu_id);
|
||||
await setCacheData(`${activePageUrl}/update`, row);
|
||||
router.push(`/panel/${activePageUrl}/update`);
|
||||
} catch (error) {
|
||||
|
|
@ -565,6 +701,10 @@ const BuildListPage: React.FC<DashboardPageProps> = React.memo((props) => {
|
|||
getSortingIcon={getSortingIcon}
|
||||
flexRender={flexRender}
|
||||
t={t}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
activePageUrl={activePageUrl}
|
||||
router={router}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import TableCardComponentImproved from "@/components/custom/content/TableCardCom
|
|||
import BuildListPage from "./builds/superuser/ListPage";
|
||||
import CreateFromBuildComponent from "./builds/superuser/CreatePage";
|
||||
import UpdateFromBuildComponent from "./builds/superuser/UpdatePage";
|
||||
import BuildPartsListPage from "./buildParts/superuser/ListPage";
|
||||
import BuildPartsListPage from "./buildParts/superuser/main/ListPage";
|
||||
import CreateFromBuildPartsComponent from "./buildParts/superuser/CreatePage";
|
||||
import UpdateFromBuildPartsComponent from "./buildParts/superuser/UpdatePage";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue