first commit
This commit is contained in:
parent
a31a77d934
commit
276abd49f8
|
|
@ -2,6 +2,5 @@ VITE_API_URL=""
|
|||
|
||||
VITE_API_TOKEN=""
|
||||
|
||||
VITE_API_SYNC_URL=""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ node_modules
|
|||
dist
|
||||
dist-content
|
||||
dist-ssr
|
||||
auto-post-facebook-extensions
|
||||
/auto-post-facebook-extensions*
|
||||
auto-post-facebook-extensions_1.0/
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -7,9 +7,9 @@
|
|||
<title>Vite + React + TS</title>
|
||||
<script type="module" crossorigin src="/assets/popup.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-CqkleIqs.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/product-api.service-BCmn_jbQ.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/product-api.service-Dn0sYl6J.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/message.service-DcR3euAR.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-DfztxwQY.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-DxX4E5UP.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.540.0",
|
||||
"p-queue": "^8.1.0",
|
||||
"react": "^19.1.1",
|
||||
|
|
@ -43,6 +44,7 @@
|
|||
"@eslint/js": "^9.33.0",
|
||||
"@types/chrome": "^0.1.4",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
|
|
@ -2575,6 +2577,13 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
|
||||
|
|
@ -5089,6 +5098,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"fs-extra": "^11.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.540.0",
|
||||
"p-queue": "^8.1.0",
|
||||
"react": "^19.1.1",
|
||||
|
|
@ -51,6 +52,7 @@
|
|||
"@eslint/js": "^9.33.0",
|
||||
"@types/chrome": "^0.1.4",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -7,9 +7,9 @@
|
|||
<title>Vite + React + TS</title>
|
||||
<script type="module" crossorigin src="/assets/popup.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-CqkleIqs.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/product-api.service-BCmn_jbQ.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/product-api.service-Dn0sYl6J.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/message.service-DcR3euAR.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-DfztxwQY.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/popup-DxX4E5UP.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
BASE_URL = 'https://int.ipsupply.com.au/api/'
|
||||
|
|
@ -25,3 +25,6 @@ auto-post-facebook-extensions
|
|||
*.sln
|
||||
*.sw?
|
||||
.env
|
||||
product-cache*
|
||||
meta.json
|
||||
data.json
|
||||
|
|
|
|||
|
|
@ -1 +1,20 @@
|
|||
[]
|
||||
[
|
||||
{
|
||||
"title": "2-Port Analog Telephone Adapter - ATA191-K9",
|
||||
"price": 220,
|
||||
"el": {},
|
||||
"date": "2025-08-21T08:34:00.451Z"
|
||||
},
|
||||
{
|
||||
"title": "Cisco 2911 Voice Bundle, Pvdm3-16, Uc License Pak, Fl- Cube10 - CISCO2911-V/K9",
|
||||
"price": 125,
|
||||
"el": {},
|
||||
"date": "2025-08-21T08:34:00.451Z"
|
||||
},
|
||||
{
|
||||
"title": "Air-ct7510-k9 - AIR-CT7510-K9",
|
||||
"price": 8562,
|
||||
"el": {},
|
||||
"date": "2025-08-21T08:34:00.451Z"
|
||||
}
|
||||
]
|
||||
|
|
@ -9,8 +9,11 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0"
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
|
|
@ -26,6 +29,23 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
|
|
@ -84,6 +104,18 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||
|
|
@ -153,6 +185,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
|
|
@ -162,6 +203,18 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
|
||||
"integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
|
@ -221,6 +274,21 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
|
|
@ -295,6 +363,63 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
|
@ -383,6 +508,21 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
|
|
@ -453,6 +593,12 @@
|
|||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
|
|
@ -592,6 +738,12 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@
|
|||
"license": "ISC",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0"
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
128
server/server.js
128
server/server.js
|
|
@ -2,7 +2,17 @@ import express from "express";
|
|||
import fs from "fs";
|
||||
import bodyParser from "body-parser";
|
||||
import cors from "cors";
|
||||
import axios from "axios";
|
||||
import path from "path";
|
||||
import _ from "lodash";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const metaPath = path.join(process.cwd(), "meta.json");
|
||||
const dataFile = "data.json";
|
||||
const cachePrefix = "product-cache-";
|
||||
const app = express();
|
||||
|
||||
// Cho phép tất cả origin gọi API
|
||||
|
|
@ -19,14 +29,41 @@ app.post("/sync", (req, res) => {
|
|||
return res.status(400).json({ error: "Dữ liệu phải là array" });
|
||||
}
|
||||
|
||||
// Thêm field date cho mỗi item
|
||||
// Chuẩn hoá dữ liệu (có thêm date)
|
||||
const mapped = data.map((item) => ({
|
||||
...item,
|
||||
date: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
// Ghi xuống file
|
||||
fs.writeFileSync("data.json", JSON.stringify(mapped, null, 2));
|
||||
// Đọc dữ liệu cũ
|
||||
let oldData = [];
|
||||
try {
|
||||
oldData = JSON.parse(fs.readFileSync(dataFile, "utf-8"));
|
||||
} catch (e) {
|
||||
oldData = [];
|
||||
}
|
||||
|
||||
// So sánh (bỏ qua field date vì nó luôn khác)
|
||||
const stripDate = (arr) => arr.map(({ date, ...rest }) => rest);
|
||||
|
||||
const oldStripped = stripDate(oldData);
|
||||
const newStripped = stripDate(mapped);
|
||||
|
||||
if (!_.isEqual(oldStripped, newStripped)) {
|
||||
// Nếu khác → xoá cache
|
||||
const files = fs.readdirSync(".");
|
||||
files.forEach((file) => {
|
||||
if (file.startsWith(cachePrefix)) {
|
||||
fs.unlinkSync(path.join(".", file));
|
||||
}
|
||||
});
|
||||
console.log("Dữ liệu thay đổi → xoá cache");
|
||||
} else {
|
||||
console.log("Dữ liệu không đổi → giữ cache");
|
||||
}
|
||||
|
||||
// Ghi dữ liệu mới
|
||||
fs.writeFileSync(dataFile, JSON.stringify(mapped, null, 2));
|
||||
|
||||
res.json({ message: "Đã lưu dữ liệu thành công", saved: mapped.length });
|
||||
});
|
||||
|
|
@ -40,6 +77,91 @@ app.get("/data", (req, res) => {
|
|||
res.json(JSON.parse(content));
|
||||
});
|
||||
|
||||
app.post("/", async (req, res) => {
|
||||
try {
|
||||
// ===== Helpers =====
|
||||
const readJSON = (path, fallback = null) => {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(path, "utf-8"));
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
const writeJSON = (path, data) => {
|
||||
try {
|
||||
fs.writeFileSync(path, JSON.stringify(data, null, 2));
|
||||
} catch (e) {
|
||||
console.warn("Không ghi được file:", e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// ===== Meta =====
|
||||
let meta = readJSON(metaPath, { total: 4000 });
|
||||
const filter = req.body?.filter || {};
|
||||
const originalFilter = { ...filter };
|
||||
const status = filter?.where?.status_listing;
|
||||
|
||||
if (["listed", "unlisted"].includes(status)) {
|
||||
filter.skip = 0;
|
||||
}
|
||||
|
||||
// ===== Cache check =====
|
||||
const cacheFile = `${cachePrefix}${JSON.stringify(originalFilter)}.json`;
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
return res.json(readJSON(cacheFile, {}));
|
||||
}
|
||||
|
||||
// ===== Load publisted data nếu cần =====
|
||||
let publistedData = [];
|
||||
if (status && meta.total) {
|
||||
filter.limit = meta.total;
|
||||
publistedData = readJSON("data.json", []);
|
||||
}
|
||||
|
||||
// ===== Call API gốc =====
|
||||
const { data } = await axios({
|
||||
headers: { Authorization: req.headers.authorization },
|
||||
url: "transferGetData",
|
||||
baseURL: process.env.BASE_URL,
|
||||
method: "POST",
|
||||
data: { ...req.body, filter },
|
||||
});
|
||||
|
||||
// Update meta
|
||||
if (typeof data.total === "number") {
|
||||
writeJSON(metaPath, { total: data.total });
|
||||
}
|
||||
|
||||
// ===== Xử lý listed/unlisted =====
|
||||
if (status === "listed" || status === "unlisted") {
|
||||
const skip = originalFilter.skip || 0;
|
||||
const limit = originalFilter.limit || data.data.length;
|
||||
|
||||
const listedCodes = new Set(
|
||||
publistedData.map((i) => (i.title + i.price || "").trim().toLowerCase())
|
||||
);
|
||||
|
||||
let filteredData = data.data.filter((item) => {
|
||||
const key = `${item.name} - ${item.code}${item.price}`.toLowerCase();
|
||||
return status === "listed"
|
||||
? listedCodes.has(key)
|
||||
: !listedCodes.has(key);
|
||||
});
|
||||
|
||||
data.total = filteredData.length;
|
||||
data.data = filteredData.slice(skip, skip + limit);
|
||||
data.filter = { ...originalFilter, skip, limit };
|
||||
|
||||
writeJSON(cacheFile, data);
|
||||
}
|
||||
|
||||
res.json(data);
|
||||
} catch (err) {
|
||||
console.error("API error:", err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log("Server chạy tại http://localhost:3000");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ class ProductApiService {
|
|||
};
|
||||
|
||||
return axios({
|
||||
url: "transferGetData",
|
||||
|
||||
method: "POST",
|
||||
data: {
|
||||
urlAPI: "/api/ebay-listing/listing-get-list",
|
||||
|
|
@ -40,8 +38,6 @@ class ProductApiService {
|
|||
|
||||
async get(data: IPost) {
|
||||
return axios({
|
||||
url: "transferGetData",
|
||||
|
||||
method: "POST",
|
||||
data: {
|
||||
urlAPI: "/api/product-model/get-info/" + data.id,
|
||||
|
|
@ -52,7 +48,6 @@ class ProductApiService {
|
|||
|
||||
async sync(data: ISyncItem[]) {
|
||||
return axios({
|
||||
baseURL: import.meta.env.VITE_API_SYNC_URL,
|
||||
url: "sync",
|
||||
method: "POST",
|
||||
data,
|
||||
|
|
@ -61,7 +56,6 @@ class ProductApiService {
|
|||
|
||||
async getPublistedProducts() {
|
||||
return axios({
|
||||
baseURL: import.meta.env.VITE_API_SYNC_URL,
|
||||
url: "data",
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export function ConfirmAlert({
|
|||
return (
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogContent aria-describedby={undefined}>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,15 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -50,17 +58,16 @@ export function PostTable() {
|
|||
const msg = new MessageService("popup");
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [categoryFilter, setCategoryFilter] = useState("all");
|
||||
const [conditionFilter, setConditionFilter] = useState("all");
|
||||
const [brandFilter, setBrandFilter] = useState("all");
|
||||
const [filter, setFilter] = useState<Record<string, any>>({});
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
|
||||
|
||||
const [search] = useDebounce(searchTerm, 400);
|
||||
|
||||
const queryKey = useMemo(
|
||||
() => ["products", { currentPage, search }],
|
||||
[currentPage, search]
|
||||
() => ["products", { currentPage, search, filter }],
|
||||
[currentPage, search, filter]
|
||||
);
|
||||
|
||||
// --- React Query fetch ---
|
||||
|
|
@ -72,6 +79,8 @@ export function PostTable() {
|
|||
skip: (currentPage - 1) * productApi.item_per_page,
|
||||
where: {
|
||||
productModelCode: searchTerm,
|
||||
status_listing:
|
||||
filter?.statusFilter === "all" ? undefined : filter.statusFilter,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
@ -102,18 +111,15 @@ export function PostTable() {
|
|||
|
||||
const clearFilters = () => {
|
||||
setSearchTerm("");
|
||||
setCategoryFilter("all");
|
||||
setConditionFilter("all");
|
||||
setBrandFilter("all");
|
||||
setStatusFilter("all");
|
||||
setFilter({});
|
||||
setCurrentPage(1);
|
||||
setIsFilterModalOpen(false);
|
||||
};
|
||||
|
||||
const activeFiltersCount = [
|
||||
categoryFilter !== "all",
|
||||
conditionFilter !== "all",
|
||||
brandFilter !== "all",
|
||||
searchTerm !== "",
|
||||
].filter(Boolean).length;
|
||||
const activeFiltersCount = [statusFilter !== "all", searchTerm !== ""].filter(
|
||||
Boolean
|
||||
).length;
|
||||
|
||||
const totalPages = useMemo(() => {
|
||||
if (!rawProducts?.total) return 0;
|
||||
|
|
@ -149,9 +155,13 @@ export function PostTable() {
|
|||
data: data,
|
||||
});
|
||||
} else {
|
||||
console.log({
|
||||
a: { ...data, ...mapToIPost({ ...res.data?.data }) },
|
||||
b: data,
|
||||
});
|
||||
msg.send("content", "popup-to-content", {
|
||||
type: "publist",
|
||||
data: mapToIPost({ ...data, ...res.data?.data }),
|
||||
data: { ...data, images: mapToIPost({ ...res.data?.data }).images },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -199,27 +209,26 @@ export function PostTable() {
|
|||
)}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogContent aria-describedby={undefined} className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Product Filters</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Select
|
||||
value={conditionFilter}
|
||||
onValueChange={setConditionFilter}
|
||||
value={statusFilter === "all" ? "" : statusFilter}
|
||||
onValueChange={setStatusFilter}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Condition" />
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent aria-describedby={undefined}>
|
||||
<SelectGroup>
|
||||
<SelectItem value="listed">Listed</SelectItem>
|
||||
<SelectItem value="unlisted">Unlisted</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={brandFilter} onValueChange={setBrandFilter}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Brand" />
|
||||
</SelectTrigger>
|
||||
</Select>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -229,7 +238,10 @@ export function PostTable() {
|
|||
Clear Filters
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setIsFilterModalOpen(false)}
|
||||
onClick={() => {
|
||||
setIsFilterModalOpen(false);
|
||||
setFilter({ statusFilter });
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
Apply
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ export default function ProductModal({
|
|||
const watchedTags = form.watch("tags");
|
||||
|
||||
const conditions = ["New", "Used - like new", "Used - good", "Used - fair"];
|
||||
const categories = ["Tools"];
|
||||
const categories = ["Tools", "Electronics & computers"];
|
||||
|
||||
const { isLoading, refetch, ...query } = usePost(data);
|
||||
const { ...query } = usePost(data);
|
||||
|
||||
// const delImageMutation = useMutation({
|
||||
// mutationFn: async (imageUrl: string) => {
|
||||
|
|
@ -307,7 +307,10 @@ export default function ProductModal({
|
|||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="md:max-w-4xl h-[95vh] flex flex-col p-0">
|
||||
<DialogContent
|
||||
aria-describedby={undefined}
|
||||
className="md:max-w-4xl h-[95vh] flex flex-col p-0"
|
||||
>
|
||||
{/* Header */}
|
||||
<DialogHeader className="p-6 border-b">
|
||||
<DialogTitle className="text-2xl font-bold">
|
||||
|
|
@ -566,7 +569,7 @@ export default function ProductModal({
|
|||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="publist"
|
||||
render={({ field }) => {
|
||||
|
|
@ -600,7 +603,7 @@ export default function ProductModal({
|
|||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</form>
|
||||
|
|
@ -610,9 +613,9 @@ export default function ProductModal({
|
|||
{/* Footer */}
|
||||
<div className="p-6 border-t bg-white flex justify-end gap-3">
|
||||
<Button type="button" variant="outline" onClick={handleClose}>
|
||||
Cancel
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
{/* <Button
|
||||
type="submit"
|
||||
onClick={form.handleSubmit(onSubmit)}
|
||||
disabled={form.formState.isSubmitting}
|
||||
|
|
@ -624,7 +627,7 @@ export default function ProductModal({
|
|||
) : (
|
||||
"Create"
|
||||
)}
|
||||
</Button>
|
||||
</Button> */}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ msgService.on<{ data: IPost }>(
|
|||
try {
|
||||
console.log("[publish] Content nhận:", payload);
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
await facebookService.handlePublist(payload.data);
|
||||
} catch (error) {
|
||||
|
|
@ -36,19 +36,19 @@ msgService.on<{ data: IPost }>(
|
|||
try {
|
||||
console.log("[unlist] Content nhận:", payload);
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
await facebookService.gotoSell();
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
await facebookService.handleDelete(payload.data);
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
const products = await facebookService.getProducts();
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
productApi.sync(products);
|
||||
} catch (error) {
|
||||
|
|
@ -69,11 +69,11 @@ msgService.on<{ data: IPost }>(
|
|||
try {
|
||||
console.log("[re-publist] Content nhận:", payload);
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
await facebookService.gotoSell();
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
await facebookService.handleRePublist(payload.data);
|
||||
} catch (error) {
|
||||
|
|
@ -88,7 +88,7 @@ msgService.on<{ data: IPost }>(
|
|||
);
|
||||
|
||||
(async () => {
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
|
||||
// Sử dụng
|
||||
thiefService
|
||||
|
|
@ -101,7 +101,7 @@ msgService.on<{ data: IPost }>(
|
|||
'[aria-label="Collection of your Marketplace items"]'
|
||||
);
|
||||
|
||||
await delayRD(1000, 2000);
|
||||
await delayRD(800, 1000);
|
||||
const products = await facebookService.getProducts();
|
||||
|
||||
console.log({ products });
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import ax from "axios";
|
|||
const axios = ax.create({
|
||||
// Dev
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + import.meta.env.VITE_API_TOKEN,
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ export function mapToIPost(raw: any): IPost {
|
|||
.map((img: any) =>
|
||||
typeof img === "string"
|
||||
? img
|
||||
: `${(import.meta.env.VITE_API_URL as string).replaceAll("api", "")}${
|
||||
img.url
|
||||
}`
|
||||
: `${import.meta.env.VITE_BASE_IMG_URL as string}${img.url}`
|
||||
)
|
||||
.slice(0, 10); // chỉ lấy tối đa 10 ảnh
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue