UPDATE
This commit is contained in:
+66
-9
@@ -28,6 +28,7 @@ export class ApiClient {
|
|||||||
constructor(baseURL: string = resolveApiBaseUrl()) {
|
constructor(baseURL: string = resolveApiBaseUrl()) {
|
||||||
this.baseURL = baseURL.replace(/\/$/, ""); // Remove trailing slash
|
this.baseURL = baseURL.replace(/\/$/, ""); // Remove trailing slash
|
||||||
this.defaultHeaders = {
|
this.defaultHeaders = {
|
||||||
|
Accept: "application/json",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -95,6 +96,57 @@ export class ApiClient {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private prepareBody(
|
||||||
|
data: any,
|
||||||
|
headers: Record<string, string>,
|
||||||
|
): BodyInit | undefined {
|
||||||
|
if (data === undefined || data === null) return undefined;
|
||||||
|
|
||||||
|
if (typeof FormData !== "undefined" && data instanceof FormData) {
|
||||||
|
delete headers["Content-Type"];
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof URLSearchParams !== "undefined" &&
|
||||||
|
data instanceof URLSearchParams
|
||||||
|
) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(data)) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = headers["Content-Type"]?.toLowerCase();
|
||||||
|
|
||||||
|
if (contentType?.includes("application/x-www-form-urlencoded")) {
|
||||||
|
return new URLSearchParams(
|
||||||
|
Object.entries(data).reduce<Record<string, string>>((acc, [key, value]) => {
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
acc[key] = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentType || contentType.includes("application/json")) {
|
||||||
|
return typeof data === "string" ? data : JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data as BodyInit;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle API response
|
* Handle API response
|
||||||
*/
|
*/
|
||||||
@@ -155,10 +207,12 @@ export class ApiClient {
|
|||||||
customHeaders?: Record<string, string>,
|
customHeaders?: Record<string, string>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${this.baseURL}${endpoint}`;
|
const url = `${this.baseURL}${endpoint}`;
|
||||||
|
const headers = this.getHeaders(customHeaders);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.getHeaders(customHeaders),
|
headers,
|
||||||
body: data ? JSON.stringify(data) : undefined,
|
body: this.prepareBody(data, headers),
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.handleResponse<T>(response);
|
return this.handleResponse<T>(response);
|
||||||
@@ -173,12 +227,11 @@ export class ApiClient {
|
|||||||
customHeaders?: Record<string, string>,
|
customHeaders?: Record<string, string>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${this.baseURL}${endpoint}`;
|
const url = `${this.baseURL}${endpoint}`;
|
||||||
const headers = { ...this.getHeaders(customHeaders) };
|
const headers = this.getHeaders(customHeaders);
|
||||||
delete headers["Content-Type"];
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body: formData,
|
body: this.prepareBody(formData, headers),
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.handleResponse<T>(response);
|
return this.handleResponse<T>(response);
|
||||||
@@ -193,10 +246,12 @@ export class ApiClient {
|
|||||||
customHeaders?: Record<string, string>,
|
customHeaders?: Record<string, string>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${this.baseURL}${endpoint}`;
|
const url = `${this.baseURL}${endpoint}`;
|
||||||
|
const headers = this.getHeaders(customHeaders);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: this.getHeaders(customHeaders),
|
headers,
|
||||||
body: data ? JSON.stringify(data) : undefined,
|
body: this.prepareBody(data, headers),
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.handleResponse<T>(response);
|
return this.handleResponse<T>(response);
|
||||||
@@ -211,10 +266,12 @@ export class ApiClient {
|
|||||||
customHeaders?: Record<string, string>,
|
customHeaders?: Record<string, string>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${this.baseURL}${endpoint}`;
|
const url = `${this.baseURL}${endpoint}`;
|
||||||
|
const headers = this.getHeaders(customHeaders);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: this.getHeaders(customHeaders),
|
headers,
|
||||||
body: data ? JSON.stringify(data) : undefined,
|
body: this.prepareBody(data, headers),
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.handleResponse<T>(response);
|
return this.handleResponse<T>(response);
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export const farmDashboardService = {
|
|||||||
try {
|
try {
|
||||||
const response = await apiClient.get<
|
const response = await apiClient.get<
|
||||||
ApiResponse<FarmDashboardConfigResponse> | FarmDashboardConfigResponse
|
ApiResponse<FarmDashboardConfigResponse> | FarmDashboardConfigResponse
|
||||||
>("/api/farm-dashboard-config");
|
>("/api/farm-dashboard-config/");
|
||||||
const raw = response && "data" in response ? response.data : response;
|
const raw = response && "data" in response ? response.data : response;
|
||||||
if (
|
if (
|
||||||
raw &&
|
raw &&
|
||||||
@@ -202,7 +202,7 @@ export const farmDashboardService = {
|
|||||||
try {
|
try {
|
||||||
const response = await apiClient.patch<
|
const response = await apiClient.patch<
|
||||||
ApiResponse<FarmDashboardConfigResponse> | FarmDashboardConfigResponse
|
ApiResponse<FarmDashboardConfigResponse> | FarmDashboardConfigResponse
|
||||||
>("/api/farm-dashboard-config", toApiRequest(data));
|
>("/api/farm-dashboard-config/", toApiRequest(data));
|
||||||
const raw = response && "data" in response ? response.data : response;
|
const raw = response && "data" in response ? response.data : response;
|
||||||
if (
|
if (
|
||||||
raw &&
|
raw &&
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ function mergeRowOrderAfterDrag(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areStringArraysEqual(left: string[], right: string[]): boolean {
|
||||||
|
if (left.length !== right.length) return false;
|
||||||
|
return left.every((value, index) => value === right[index]);
|
||||||
|
}
|
||||||
|
|
||||||
const FarmDashboardWrapper = () => {
|
const FarmDashboardWrapper = () => {
|
||||||
const t = useTranslations("farmDashboard");
|
const t = useTranslations("farmDashboard");
|
||||||
const { setSlotContent } = useContext(NavbarSlotContext);
|
const { setSlotContent } = useContext(NavbarSlotContext);
|
||||||
@@ -149,7 +154,10 @@ const FarmDashboardWrapper = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
const disabledSet = new Set(config.disabledCardIds);
|
const disabledSet = useMemo(
|
||||||
|
() => new Set(config.disabledCardIds),
|
||||||
|
[config.disabledCardIds],
|
||||||
|
);
|
||||||
|
|
||||||
const hasVisibleCard = useCallback(
|
const hasVisibleCard = useCallback(
|
||||||
(rowId: string) => {
|
(rowId: string) => {
|
||||||
@@ -160,7 +168,10 @@ const FarmDashboardWrapper = () => {
|
|||||||
[config.disabledCardIds],
|
[config.disabledCardIds],
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleRowOrder = config.rowOrder.filter(hasVisibleCard);
|
const visibleRowOrder = useMemo(
|
||||||
|
() => config.rowOrder.filter(hasVisibleCard),
|
||||||
|
[config.rowOrder, hasVisibleCard],
|
||||||
|
);
|
||||||
|
|
||||||
const [containerRef, orderedRows, setOrderedRows] = useDragAndDrop(
|
const [containerRef, orderedRows, setOrderedRows] = useDragAndDrop(
|
||||||
visibleRowOrder,
|
visibleRowOrder,
|
||||||
@@ -198,18 +209,19 @@ const FarmDashboardWrapper = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (areStringArraysEqual(orderedRows, visibleRowOrder)) return;
|
||||||
setOrderedRows(visibleRowOrder);
|
setOrderedRows(visibleRowOrder);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [orderedRows, setOrderedRows, visibleRowOrder]);
|
||||||
}, [visibleRowOrder]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
if (JSON.stringify(orderedRows) === JSON.stringify(visibleRowOrder)) return;
|
if (areStringArraysEqual(orderedRows, visibleRowOrder)) return;
|
||||||
const newRowOrder = mergeRowOrderAfterDrag(
|
const newRowOrder = mergeRowOrderAfterDrag(
|
||||||
config.rowOrder,
|
config.rowOrder,
|
||||||
orderedRows,
|
orderedRows,
|
||||||
visibleRowOrder,
|
visibleRowOrder,
|
||||||
);
|
);
|
||||||
|
if (areStringArraysEqual(newRowOrder, config.rowOrder)) return;
|
||||||
setConfig((prev) => ({ ...prev, rowOrder: newRowOrder }));
|
setConfig((prev) => ({ ...prev, rowOrder: newRowOrder }));
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
farmDashboardService
|
farmDashboardService
|
||||||
@@ -217,8 +229,7 @@ const FarmDashboardWrapper = () => {
|
|||||||
.then((updated) => setConfig(updated))
|
.then((updated) => setConfig(updated))
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => setSaving(false));
|
.finally(() => setSaving(false));
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [config.rowOrder, loading, orderedRows, visibleRowOrder]);
|
||||||
}, [orderedRows]);
|
|
||||||
|
|
||||||
const handleToggleDragReorder = useCallback((enabled: boolean) => {
|
const handleToggleDragReorder = useCallback((enabled: boolean) => {
|
||||||
setConfig((prev) => ({ ...prev, enableDragReorder: enabled }));
|
setConfig((prev) => ({ ...prev, enableDragReorder: enabled }));
|
||||||
|
|||||||
Reference in New Issue
Block a user