ok
This commit is contained in:
58
note/detailUI.md
Normal file
58
note/detailUI.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
**Overlay/Status**
|
||||||
|
- Loader Overlay: layar penuh dengan spinner `ThreeDots` dan pesan `screenMessag
|
||||||
|
e`; muncul saat `loading` true.
|
||||||
|
- Grayscale State: seluruh konten `div.Cafe` menjadi abu-abu saat `isExceededDea
|
||||||
|
dline` true (kelas `grayscale`).
|
||||||
|
- Modal Pesan: “Kafe sedang tidak tersedia” dapat muncul via `setModal("message"
|
||||||
|
, ...)` ketika `isExceededDeadline` dari socket; modalnya dikelola global (di lu
|
||||||
|
ar file ini).
|
||||||
|
|
||||||
|
**Header**
|
||||||
|
- Bar Atas “Menu”: judul “Menu”, info toko (nama, gambar), profil user, opsi log
|
||||||
|
out, akses pegawai, table code.
|
||||||
|
- Edit Mode Toggle: kontrol untuk mengaktifkan/menonaktifkan mode edit kategori/
|
||||||
|
item (prop `isEditMode` + `setIsEditMode`).
|
||||||
|
|
||||||
|
**Branding/Watermark**
|
||||||
|
- Dev Watermark (atas): `div.Watermark` kecil hanya tampil jika `API_BASE_URL` b
|
||||||
|
ukan domain resmi prod/dev (indikator environment).
|
||||||
|
- Footer Watermark: komponen `<Watermark />` di bagian paling bawah halaman.
|
||||||
|
|
||||||
|
**Music**
|
||||||
|
- Music Player: widget pemutar/antrian lagu dengan dukungan Spotify; menampilkan
|
||||||
|
status login Spotify (`isSpotifyNeedLogin`) dan antrian (`queue`), interaksi vi
|
||||||
|
a `socket`.
|
||||||
|
|
||||||
|
**Kategori (ItemTypeLister)**
|
||||||
|
- Daftar Kategori: list tipe menu (nama + gambar tipe, visibilitas).
|
||||||
|
- Filter Kategori: memilih 1 kategori untuk menyaring tampilan item (`filterId`)
|
||||||
|
.
|
||||||
|
- Edit Kategori: saat `isEditMode` aktif, bisa memilih tipe yang sedang diedit (
|
||||||
|
`beingEditedType`) dan mengubah urutan tipe (via kontrol di ItemLister).
|
||||||
|
|
||||||
|
**Daftar Item per Kategori (ItemLister x N)**
|
||||||
|
- Section per Kategori: render berulang untuk setiap tipe yang lolos filter. Men
|
||||||
|
ampilkan:
|
||||||
|
- Judul/nama kategori dan gambar (bila ada).
|
||||||
|
- Daftar item dalam kategori (nama, harga, promo, deskripsi, gambar, visibilit
|
||||||
|
as).
|
||||||
|
- Aksi Item:
|
||||||
|
- Tambah item: tombol/form untuk membuat item (owner/akses yang berwenang).
|
||||||
|
- Ubah item: edit nama, harga, promo, deskripsi, gambar.
|
||||||
|
- Reorder Kategori: panah/aksi “naik/turun” pada section untuk memindahkan posis
|
||||||
|
i kategori (memanggil `moveItemType*`).
|
||||||
|
- Raw Mode: jika tidak edit dan filter spesifik aktif, `raw` true untuk gaya tam
|
||||||
|
pilan ringkas.
|
||||||
|
|
||||||
|
**Sticky Bar (Keranjang & Transaksi)**
|
||||||
|
- Tombol Cart (kiri, utama): muncul jika bukan edit mode dan (user login atau ke
|
||||||
|
ranjang > 0).
|
||||||
|
- Menampilkan jumlah item (dengan “+” jika ada transaksi terakhir), total harg
|
||||||
|
a “Rp{totalPrice}” atau teks “Open bill” (jika `lastTransaction.payment_type ==
|
||||||
|
'paylater'`).
|
||||||
|
- Posisi: sticky di bawah (offset bottom ~40px), lebar responsif.
|
||||||
|
- Tombol Transactions (kanan, kecil): hanya muncul jika user login; navigasi ke
|
||||||
|
riwayat transaksi.
|
||||||
|
|
||||||
|
|
||||||
|
material list, material
|
||||||
219
package-lock.json
generated
219
package-lock.json
generated
@@ -14,12 +14,14 @@
|
|||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"apexcharts": "^5.3.4",
|
||||||
"caniuse-lite": "^1.0.30001690",
|
"caniuse-lite": "^1.0.30001690",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
|
"lucide-react": "^0.541.0",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -3905,171 +3907,6 @@
|
|||||||
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@mui/core-downloads-tracker": {
|
|
||||||
"version": "7.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.1.tgz",
|
|
||||||
"integrity": "sha512-+mIK1Z0BhOaQ0vCgOkT1mSrIpEHLo338h4/duuL4TBLXPvUMit732mnwJY3W40Avy30HdeSfwUAAGRkKmwRaEQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/material": {
|
|
||||||
"version": "7.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.1.tgz",
|
|
||||||
"integrity": "sha512-Xf6Shbo03YmcBedZMwSpEFOwpYDtU7tC+rhAHTrA9FHk0FpsDqiQ9jUa1j/9s3HLs7KWb5mDcGnlwdh9Q9KAag==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.28.2",
|
|
||||||
"@mui/core-downloads-tracker": "^7.3.1",
|
|
||||||
"@mui/system": "^7.3.1",
|
|
||||||
"@mui/types": "^7.4.5",
|
|
||||||
"@mui/utils": "^7.3.1",
|
|
||||||
"@popperjs/core": "^2.11.8",
|
|
||||||
"@types/react-transition-group": "^4.4.12",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"csstype": "^3.1.3",
|
|
||||||
"prop-types": "^15.8.1",
|
|
||||||
"react-is": "^19.1.1",
|
|
||||||
"react-transition-group": "^4.4.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/react": "^11.5.0",
|
|
||||||
"@emotion/styled": "^11.3.0",
|
|
||||||
"@mui/material-pigment-css": "^7.3.1",
|
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
||||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@emotion/styled": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@mui/material-pigment-css": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/private-theming": {
|
|
||||||
"version": "7.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.1.tgz",
|
|
||||||
"integrity": "sha512-WU3YLkKXii/x8ZEKnrLKsPwplCVE11yZxUvlaaZSIzCcI3x2OdFC8eMlNy74hVeUsYQvzzX1Es/k4ARPlFvpPQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.28.2",
|
|
||||||
"@mui/utils": "^7.3.1",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/styled-engine": {
|
|
||||||
"version": "7.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.1.tgz",
|
|
||||||
"integrity": "sha512-Nqo6OHjvJpXJ1+9TekTE//+8RybgPQUKwns2Lh0sq+8rJOUSUKS3KALv4InSOdHhIM9Mdi8/L7LTF1/Ky6D6TQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.28.2",
|
|
||||||
"@emotion/cache": "^11.14.0",
|
|
||||||
"@emotion/serialize": "^1.3.3",
|
|
||||||
"@emotion/sheet": "^1.4.0",
|
|
||||||
"csstype": "^3.1.3",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/react": "^11.4.1",
|
|
||||||
"@emotion/styled": "^11.3.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@emotion/styled": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/system": {
|
|
||||||
"version": "7.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz",
|
|
||||||
"integrity": "sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.28.2",
|
|
||||||
"@mui/private-theming": "^7.3.1",
|
|
||||||
"@mui/styled-engine": "^7.3.1",
|
|
||||||
"@mui/types": "^7.4.5",
|
|
||||||
"@mui/utils": "^7.3.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"csstype": "^3.1.3",
|
|
||||||
"prop-types": "^15.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/react": "^11.5.0",
|
|
||||||
"@emotion/styled": "^11.3.0",
|
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@emotion/styled": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/types": {
|
"node_modules/@mui/types": {
|
||||||
"version": "7.4.5",
|
"version": "7.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz",
|
||||||
@@ -4638,7 +4475,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz",
|
||||||
"integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==",
|
"integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@svgdotjs/svg.js": "^3.2.4"
|
"@svgdotjs/svg.js": "^3.2.4"
|
||||||
}
|
}
|
||||||
@@ -4648,7 +4484,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz",
|
||||||
"integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==",
|
"integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@svgdotjs/svg.js": "^3.2.4"
|
"@svgdotjs/svg.js": "^3.2.4"
|
||||||
},
|
},
|
||||||
@@ -4661,7 +4496,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz",
|
||||||
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
|
"integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/Fuzzyma"
|
"url": "https://github.com/sponsors/Fuzzyma"
|
||||||
@@ -4672,7 +4506,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz",
|
||||||
"integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==",
|
"integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14.18"
|
"node": ">= 14.18"
|
||||||
},
|
},
|
||||||
@@ -4686,7 +4519,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz",
|
||||||
"integrity": "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==",
|
"integrity": "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14.18"
|
"node": ">= 14.18"
|
||||||
},
|
},
|
||||||
@@ -4924,26 +4756,6 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@testing-library/dom": {
|
|
||||||
"version": "10.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
|
||||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/code-frame": "^7.10.4",
|
|
||||||
"@babel/runtime": "^7.12.5",
|
|
||||||
"@types/aria-query": "^5.0.1",
|
|
||||||
"aria-query": "5.3.0",
|
|
||||||
"dom-accessibility-api": "^0.5.9",
|
|
||||||
"lz-string": "^1.5.0",
|
|
||||||
"picocolors": "1.1.1",
|
|
||||||
"pretty-format": "^27.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@testing-library/jest-dom": {
|
"node_modules/@testing-library/jest-dom": {
|
||||||
"version": "5.17.0",
|
"version": "5.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
|
||||||
@@ -5976,8 +5788,7 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@zxing/browser": {
|
"node_modules/@zxing/browser": {
|
||||||
"version": "0.0.7",
|
"version": "0.0.7",
|
||||||
@@ -6315,7 +6126,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.3.4.tgz",
|
||||||
"integrity": "sha512-N0gNh8uLu/BN8N+BCphNK+gZAoSoUtDDn1jFGB+3+EMcv8s6vajuP3W0g4dMLTRp6chFkjMmQK3uD8pz4ISmLA==",
|
"integrity": "sha512-N0gNh8uLu/BN8N+BCphNK+gZAoSoUtDDn1jFGB+3+EMcv8s6vajuP3W0g4dMLTRp6chFkjMmQK3uD8pz4ISmLA==",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@svgdotjs/svg.draggable.js": "^3.0.4",
|
"@svgdotjs/svg.draggable.js": "^3.0.4",
|
||||||
"@svgdotjs/svg.filter.js": "^3.0.8",
|
"@svgdotjs/svg.filter.js": "^3.0.8",
|
||||||
@@ -15199,6 +15009,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.541.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.541.0.tgz",
|
||||||
|
"integrity": "sha512-s0Vircsu5WaGv2KoJZ5+SoxiAJ3UXV5KqEM3eIFDHaHkcLIFdIWgXtZ412+Gh02UsdS7Was+jvEpBvPCWQISlg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
@@ -20825,20 +20644,6 @@
|
|||||||
"is-typedarray": "^1.0.0"
|
"is-typedarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||||
|
|||||||
@@ -10,12 +10,14 @@
|
|||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"apexcharts": "^5.3.4",
|
||||||
"caniuse-lite": "^1.0.30001690",
|
"caniuse-lite": "^1.0.30001690",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"html-to-image": "^1.11.11",
|
"html-to-image": "^1.11.11",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jsqr": "^1.4.0",
|
"jsqr": "^1.4.0",
|
||||||
|
"lucide-react": "^0.541.0",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
10
src/App.css
10
src/App.css
@@ -1,6 +1,14 @@
|
|||||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Aboreto&family=Rubik+Doodle+Shadow&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Aboreto&family=Rubik+Doodle+Shadow&display=swap");
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@200;300;400;500;600;700;800&ital,wght@0,200..800;1,200..800&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@200;300;400;500;600;700;800&ital,wght@0,200..800;1,200..800&display=swap");
|
||||||
|
:root {
|
||||||
|
--brand-primary: #73a585; /* general brand (e.g., music player) */
|
||||||
|
--brand-sage: #6B8F71; /* sage green for active category */
|
||||||
|
--brand-sage-50: #F0F6F2; /* very light hover bg */
|
||||||
|
--brand-sage-100: #E9F3ED; /* light hover bg */
|
||||||
|
--brand-sage-hover: #7FAE7D; /* hover for filled buttons */
|
||||||
|
--brand-sage-muted: #CFD8D3; /* disabled button */
|
||||||
|
}
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
@@ -54,6 +62,8 @@ body {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* removed two-column layout; reverted to single column */
|
||||||
|
|
||||||
.App-link {
|
.App-link {
|
||||||
color: #61dafb;
|
color: #61dafb;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useLocation } from "react-router-dom";
|
|||||||
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
import { useNavigationHelpers } from "../helpers/navigationHelpers";
|
||||||
import Switch from "react-switch";
|
import Switch from "react-switch";
|
||||||
|
|
||||||
|
// Restore original gradient background for header container when shopName exists
|
||||||
const HeaderBarbackground = styled.div`
|
const HeaderBarbackground = styled.div`
|
||||||
${({ shopName }) =>
|
${({ shopName }) =>
|
||||||
shopName &&
|
shopName &&
|
||||||
@@ -19,7 +20,7 @@ const HeaderBar = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 15px;
|
padding: 12px 14px;
|
||||||
color: black;
|
color: black;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
@@ -33,30 +34,15 @@ const HeaderBar = styled.div`
|
|||||||
const Title = styled.h2`
|
const Title = styled.h2`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size:${(props) => (props.HeaderSize)};
|
font-size:${(props) => (props.HeaderSize)};
|
||||||
color: rgba(88, 55, 50, 1);
|
color: rgba(45, 45, 45, 1);
|
||||||
text-transform: uppercase;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ProfileName = styled.h2`
|
// SubTitle removed per redesign (no subtitle below cafe name)
|
||||||
position: absolute;
|
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
// Deprecated the animated ProfileName in favor of a cleaner layout
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 30px;
|
|
||||||
z-index: 199;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
animation: ${(props) => {
|
|
||||||
if (props.animate === "grow") return gg;
|
|
||||||
if (props.animate === "shrink") return ss;
|
|
||||||
return nn;
|
|
||||||
}}
|
|
||||||
0.5s forwards;
|
|
||||||
text-align: left;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const nn = keyframes`
|
const nn = keyframes`
|
||||||
0% {
|
0% {
|
||||||
@@ -103,22 +89,17 @@ const ss = keyframes`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ProfileImage = styled.img`
|
const CafeAvatar = styled.img`
|
||||||
position: relative;
|
width: clamp(32px, 5vw, 56px);
|
||||||
width: 60px;
|
height: clamp(32px, 5vw, 56px);
|
||||||
height: 60px;
|
border-radius: 8px;
|
||||||
border-radius: 50%;
|
object-fit: cover;
|
||||||
object-fit: contain;
|
background: #f2f2f2;
|
||||||
cursor: pointer;
|
margin-left: 8px; /* extra left padding so it’s not too tight */
|
||||||
z-index: 199;
|
|
||||||
animation: ${(props) => {
|
|
||||||
if (props.animate === "grow") return g;
|
|
||||||
if (props.animate === "shrink") return s;
|
|
||||||
return "none";
|
|
||||||
}}
|
|
||||||
0.5s forwards;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// User initial avatar removed; only cafe image is shown on the left
|
||||||
|
|
||||||
const g = keyframes`
|
const g = keyframes`
|
||||||
0% {
|
0% {
|
||||||
top: 0px;
|
top: 0px;
|
||||||
@@ -149,62 +130,43 @@ const s = keyframes`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/* Replace bubble-like animation with subtle fade/slide */
|
||||||
const grow = keyframes`
|
const grow = keyframes`
|
||||||
0% {
|
0% { opacity: 0; transform: translateY(-6px) scale(0.98); }
|
||||||
right: 12px;
|
100% { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-top-left-radius: 50%;
|
|
||||||
border-bottom-left-radius: 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
right: 28px;
|
|
||||||
width: 300px;
|
|
||||||
border-top-left-radius: 15px;
|
|
||||||
border-bottom-left-radius: 15px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const shrink = keyframes`
|
const shrink = keyframes`
|
||||||
0% {
|
0% { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
right: 12px;
|
100% { opacity: 0; transform: translateY(-6px) scale(0.98); }
|
||||||
width: 300px;
|
|
||||||
height: auto;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
right: 28px;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
const Rectangle = styled.div`
|
const Rectangle = styled.div`
|
||||||
overflow-y: hidden;
|
overflow-y: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 39px;
|
top: calc(100% + 8px);
|
||||||
right: 12px;
|
right: 0;
|
||||||
width: 200px;
|
width: 240px;
|
||||||
max-height: 87vh; /* or another appropriate value */
|
max-height: 75vh;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
z-index: 198;
|
z-index: 198;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
animation: ${(props) => (props.animate === "grow" ? grow : shrink)} 0.5s
|
border: 1px solid #f0f0f0;
|
||||||
forwards;
|
border-radius: 12px;
|
||||||
padding: 10px;
|
animation: ${(props) => (props.animate === "grow" ? grow : shrink)} 0.2s forwards;
|
||||||
|
padding: 10px 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #393939;
|
color: #393939;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ChildContainer = styled.div`
|
const ChildContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-end;
|
align-items: stretch;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
padding-top: 70px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ChildWrapper = styled.div`
|
const ChildWrapper = styled.div`
|
||||||
@@ -213,25 +175,88 @@ const ChildWrapper = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
const Child = styled.div`
|
const Child = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
min-height: 36px;
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
padding: ${(props) => (props.hasChildren ? '8px 0 4px' : '8px 4px')};
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.hasChildren
|
props.hasChildren
|
||||||
? `
|
? `
|
||||||
margin-top: 14px;
|
margin-top: 10px;
|
||||||
border-top: 0.5px solid #a5a5a5;
|
border-top: 0.5px solid #e9e9e9;
|
||||||
height: auto;
|
height: auto;
|
||||||
`
|
`
|
||||||
: `
|
: `
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const LeftGroup = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CenterGroup = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none; /* so clicks pass through center when needed */
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CafeInfo = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RightGroup = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CategoryLabel = styled.div`
|
||||||
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #6B8F71;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
padding: 6px 2px;
|
||||||
|
pointer-events: none;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HamburgerButton = styled.button`
|
||||||
|
width: clamp(32px, 4.5vw, 52px);
|
||||||
|
height: clamp(32px, 4.5vw, 52px);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
background: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
& > svg {
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HamburgerIcon = () => (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="3" y="6" width="18" height="2" rx="1" fill="#2d2d2d"/>
|
||||||
|
<rect x="3" y="11" width="18" height="2" rx="1" fill="#2d2d2d"/>
|
||||||
|
<rect x="3" y="16" width="18" height="2" rx="1" fill="#2d2d2d"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const Header = ({
|
const Header = ({
|
||||||
HeaderText,
|
HeaderText,
|
||||||
@@ -261,10 +286,10 @@ const Header = ({
|
|||||||
const [guestSideOf, setGuestSideOf] = useState(null);
|
const [guestSideOf, setGuestSideOf] = useState(null);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const handleImageClick = () => {
|
const toggleMenu = () => {
|
||||||
if (showRectangle) {
|
if (showRectangle) {
|
||||||
setAnimate("shrink");
|
setAnimate("shrink");
|
||||||
setTimeout(() => setShowRectangle(false), 500);
|
setTimeout(() => setShowRectangle(false), 200);
|
||||||
} else {
|
} else {
|
||||||
setAnimate("grow");
|
setAnimate("grow");
|
||||||
setShowRectangle(true);
|
setShowRectangle(true);
|
||||||
@@ -274,15 +299,14 @@ const Header = ({
|
|||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
if (rectangleRef.current && !rectangleRef.current.contains(event.target)) {
|
if (rectangleRef.current && !rectangleRef.current.contains(event.target)) {
|
||||||
setAnimate("shrink");
|
setAnimate("shrink");
|
||||||
setTimeout(() => setShowRectangle(false), 500);
|
setTimeout(() => setShowRectangle(false), 200);
|
||||||
rectangleRef.current.style.overflow = "hidden";
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (showRectangle) {
|
if (showRectangle) {
|
||||||
setAnimate("shrink");
|
setAnimate("shrink");
|
||||||
setTimeout(() => setShowRectangle(false), 500);
|
setTimeout(() => setShowRectangle(false), 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -321,38 +345,48 @@ const Header = ({
|
|||||||
// Otherwise, use the possessive function
|
// Otherwise, use the possessive function
|
||||||
return `${cafeName}'s menu`;
|
return `${cafeName}'s menu`;
|
||||||
};
|
};
|
||||||
return (
|
|
||||||
|
const formatCafeName = (name) => {
|
||||||
|
if (!name) return name;
|
||||||
|
return name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
};
|
||||||
|
return (
|
||||||
<HeaderBarbackground shopName={shopName}>
|
<HeaderBarbackground shopName={shopName}>
|
||||||
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}>
|
<HeaderBar HeaderMargin={HeaderMargin} shopName={shopName}>
|
||||||
<Title HeaderSize={HeaderSize}>
|
<LeftGroup>
|
||||||
{shopName == null
|
<CafeAvatar
|
||||||
? HeaderText == null
|
src={shopImage && !shopImage.includes('undefined') ? shopImage : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
|
||||||
? "kedaimaster"
|
alt="Cafe"
|
||||||
: HeaderText
|
/>
|
||||||
: shopName}
|
</LeftGroup>
|
||||||
</Title>
|
<CenterGroup>
|
||||||
<div style={{ visibility: showProfile ? "visible" : "hidden" }}>
|
<Title HeaderSize={HeaderSize}>
|
||||||
<ProfileImage
|
{formatCafeName(
|
||||||
src={shopImage && !shopImage.includes('undefined') ? shopImage || 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s' : "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS-DjX_bGBax4NL14ULvkAdU4FP3FKoWXWu5w&s"}
|
shopName == null
|
||||||
alt="Profile"
|
? HeaderText == null
|
||||||
onClick={user.username !== undefined ? handleImageClick : null}
|
? "kedaimaster"
|
||||||
animate={showRectangle && animate}
|
: HeaderText
|
||||||
/>
|
: shopName
|
||||||
<ProfileName animate={showRectangle && animate}>
|
)}
|
||||||
{showProfile && user.username !== undefined ? user.username : "guest"}
|
</Title>
|
||||||
</ProfileName>
|
</CenterGroup>
|
||||||
{showRectangle && (
|
|
||||||
<Rectangle ref={rectangleRef} animate={animate}>
|
<RightGroup style={{ visibility: showProfile ? "visible" : "hidden", position: 'relative' }}>
|
||||||
<ChildContainer>
|
<HamburgerButton onClick={toggleMenu} aria-label="Open menu">
|
||||||
|
<HamburgerIcon />
|
||||||
|
</HamburgerButton>
|
||||||
|
{showRectangle && (
|
||||||
|
<Rectangle ref={rectangleRef} animate={animate}>
|
||||||
|
<ChildContainer>
|
||||||
{guestSideOfClerk && guestSideOfClerk.clerkUsername && (
|
{guestSideOfClerk && guestSideOfClerk.clerkUsername && (
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
this is the guest side of {guestSideOfClerk.clerkUsername}
|
this is the guest side of {guestSideOfClerk.clerkUsername}
|
||||||
</Child>
|
</Child>
|
||||||
)}
|
)}
|
||||||
{user.username !== undefined && (
|
{user.username !== undefined && (
|
||||||
<Child onClick={() => setModal("edit_account")}>
|
<CategoryLabel>Kelola akun</CategoryLabel>
|
||||||
Kelola akun
|
|
||||||
</Child>
|
|
||||||
)}
|
)}
|
||||||
{user.roleId == 0 && (
|
{user.roleId == 0 && (
|
||||||
<Child onClick={()=>setModal('create_coupon', {})}>Buat Voucher</Child>)}
|
<Child onClick={()=>setModal('create_coupon', {})}>Buat Voucher</Child>)}
|
||||||
@@ -364,9 +398,9 @@ const Header = ({
|
|||||||
user.roleId === 1 && (
|
user.roleId === 1 && (
|
||||||
<>
|
<>
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
<Child>
|
<CategoryLabel>
|
||||||
{shopName}
|
{formatCafeName(shopName)}
|
||||||
</Child>
|
</CategoryLabel>
|
||||||
<Child>
|
<Child>
|
||||||
Mode pengembangan
|
Mode pengembangan
|
||||||
<Switch
|
<Switch
|
||||||
@@ -381,7 +415,7 @@ const Header = ({
|
|||||||
</Child>
|
</Child>
|
||||||
|
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
<Child>Konfigurasi</Child>
|
<CategoryLabel>Konfigurasi</CategoryLabel>
|
||||||
<Child onClick={() => setModal("welcome_config")}>
|
<Child onClick={() => setModal("welcome_config")}>
|
||||||
Desain kafe
|
Desain kafe
|
||||||
</Child>
|
</Child>
|
||||||
@@ -393,7 +427,7 @@ const Header = ({
|
|||||||
</Child>
|
</Child>
|
||||||
</Child>
|
</Child>
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
<Child>Kasir</Child>
|
<CategoryLabel>Kasir</CategoryLabel>
|
||||||
<Child onClick={() => setModal("create_clerk")}>
|
<Child onClick={() => setModal("create_clerk")}>
|
||||||
+ Tambah
|
+ Tambah
|
||||||
</Child>
|
</Child>
|
||||||
@@ -420,7 +454,7 @@ const Header = ({
|
|||||||
user.cafeId == shopId &&
|
user.cafeId == shopId &&
|
||||||
user.roleId === 2 && (
|
user.roleId === 2 && (
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
<Child>{shopName}</Child>
|
<CategoryLabel>{formatCafeName(shopName)}</CategoryLabel>
|
||||||
|
|
||||||
<Child>
|
<Child>
|
||||||
Mode pengembangan
|
Mode pengembangan
|
||||||
@@ -435,7 +469,7 @@ const Header = ({
|
|||||||
</Child>
|
</Child>
|
||||||
|
|
||||||
<Child hasChildren>
|
<Child hasChildren>
|
||||||
<Child>Konfigurasi</Child>
|
<CategoryLabel>Konfigurasi</CategoryLabel>
|
||||||
<Child onClick={() => setModal("welcome_config")}>
|
<Child onClick={() => setModal("welcome_config")}>
|
||||||
Desain kafe
|
Desain kafe
|
||||||
</Child>
|
</Child>
|
||||||
@@ -478,11 +512,12 @@ const Header = ({
|
|||||||
{user.username !== undefined && (
|
{user.username !== undefined && (
|
||||||
<Child hasChildren ><Child onClick={isLogout}>Logout</Child></Child>
|
<Child hasChildren ><Child onClick={isLogout}>Logout</Child></Child>
|
||||||
)}
|
)}
|
||||||
</ChildContainer>
|
</ChildContainer>
|
||||||
</Rectangle>
|
</Rectangle>
|
||||||
)}
|
)}
|
||||||
</div>
|
</RightGroup>
|
||||||
</HeaderBar></HeaderBarbackground>
|
</HeaderBar>
|
||||||
|
</HeaderBarbackground>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ const Item = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatCurrency = (value) => {
|
||||||
|
const num = Number(value) || 0;
|
||||||
|
return num.toLocaleString('id-ID');
|
||||||
|
};
|
||||||
|
|
||||||
const handlePriceChange = (event) => {
|
const handlePriceChange = (event) => {
|
||||||
setItemPrice(event.target.value);
|
setItemPrice(event.target.value);
|
||||||
};
|
};
|
||||||
@@ -93,23 +98,17 @@ const Item = ({
|
|||||||
<div className={`${!last && !forInvoice ? styles.notLast : ""}`}>
|
<div className={`${!last && !forInvoice ? styles.notLast : ""}`}>
|
||||||
<div className={`${styles.item} ${forInvoice ? styles.itemInvoice : ""} `}>
|
<div className={`${styles.item} ${forInvoice ? styles.itemInvoice : ""} `}>
|
||||||
{!forInvoice && (
|
{!forInvoice && (
|
||||||
// <div className={styles.imageContainer}>
|
|
||||||
<img
|
<img
|
||||||
src={
|
src={previewUrl}
|
||||||
previewUrl
|
|
||||||
}
|
|
||||||
onError={({ currentTarget }) => {
|
onError={({ currentTarget }) => {
|
||||||
currentTarget.onerror = null; // prevents looping
|
currentTarget.onerror = null;
|
||||||
currentTarget.src =
|
currentTarget.src =
|
||||||
"https://png.pngtree.com/png-vector/20221125/ourmid/pngtree-no-image-available-icon-flatvector-illustration-pic-design-profile-vector-png-image_40966566.jpg";
|
"https://png.pngtree.com/png-vector/20221125/ourmid/pngtree-no-image-available-icon-flatvector-illustration-pic-design-profile-vector-png-image_40966566.jpg";
|
||||||
}}
|
}}
|
||||||
alt={itemName}
|
alt={itemName}
|
||||||
style={{
|
style={{ filter: !isAvailable ? "grayscale(100%)" : "none" }}
|
||||||
filter: !isAvailable ? "grayscale(100%)" : "none",
|
|
||||||
}}
|
|
||||||
className={styles.imageContainer}
|
className={styles.imageContainer}
|
||||||
/>
|
/>
|
||||||
// </div>
|
|
||||||
)}
|
)}
|
||||||
<div className={styles.itemDetails}>
|
<div className={styles.itemDetails}>
|
||||||
{forInvoice &&
|
{forInvoice &&
|
||||||
@@ -141,168 +140,63 @@ const Item = ({
|
|||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
disabled={!blank && !isBeingEdit}
|
disabled={!blank && !isBeingEdit}
|
||||||
/> */}
|
/> */}
|
||||||
<h3 style={{
|
<div style={{ marginRight: forInvoice ? 10 : 0 }}>
|
||||||
textTransform: 'capitalize',
|
<h3 className={styles.title} style={{ width: forInvoice ? 160 : 'auto' }}>{itemName}</h3>
|
||||||
margin: `${forInvoice ? '13px 0px 10px 10px' : '5px 0px 10px 10px'}`,
|
{!forInvoice && (
|
||||||
fontSize: '16px',
|
<div className={styles.priceRow}>
|
||||||
display: '-webkit-box',
|
{promoPrice && promoPrice != 0 && promoPrice != '' ? (
|
||||||
WebkitBoxOrient: 'vertical',
|
<>
|
||||||
overflow: 'hidden',
|
<div className={styles.promoBadge} style={{ background: !isAvailable ? 'gray' : undefined }}>
|
||||||
WebkitLineClamp: 2,
|
Promo {(((initialPrice - promoPrice) / initialPrice) * 100).toFixed(0)}%
|
||||||
textOverflow: 'ellipsis',
|
</div>
|
||||||
width: `${forInvoice? '160px' : 'unset'}`
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
}}>
|
<span className={styles.priceNow}>Rp {formatCurrency(promoPrice)}</span>
|
||||||
{itemName}
|
<span className={styles.priceOld}>Rp {formatCurrency(initialPrice)}</span>
|
||||||
</h3>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<span className={styles.priceNow}>Rp {formatCurrency(initialPrice)}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{forInvoice && (
|
{forInvoice && (
|
||||||
<>
|
<>
|
||||||
<p className={styles.multiplySymbol}>x</p>
|
<p className={styles.multiplySymbol}>x</p>
|
||||||
<p className={styles.qtyInvoice}>{itemQty}</p>
|
<p className={styles.qtyInvoice}>{itemQty}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!forInvoice && (
|
{!forInvoice && (
|
||||||
// <input
|
!isBeingEdit && itemQty > 0 ? (
|
||||||
// className={`${styles.itemPrice} ${
|
|
||||||
// isBeingEdit || blank ? styles.blank : styles.notblank
|
|
||||||
// } ${!isAvailable ? styles.disabled : ""}`}
|
|
||||||
// value={itemPrice}
|
|
||||||
// placeholder="Harga"
|
|
||||||
// onChange={handlePriceChange}
|
|
||||||
// disabled={!blank && !isBeingEdit}
|
|
||||||
// />
|
|
||||||
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
alignItems: 'center',
|
|
||||||
color: 'rgb(28, 29, 29)',
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: '1rem',
|
|
||||||
marginLeft: 10
|
|
||||||
}}>
|
|
||||||
{promoPrice && promoPrice != 0 && promoPrice != '' ?
|
|
||||||
<>
|
|
||||||
<div style={{
|
|
||||||
position: 'relative',
|
|
||||||
marginTop: '0.125rem',
|
|
||||||
display: 'flex',
|
|
||||||
width: '87px',
|
|
||||||
alignItems: 'center',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
borderRadius: '9999px',
|
|
||||||
backgroundColor: !isAvailable ? 'gray' : 'unset',
|
|
||||||
backgroundImage: isAvailable && 'linear-gradient(to right, #e52535, #fe6d78)',
|
|
||||||
padding: '0.25rem 0rem',
|
|
||||||
color: 'rgb(255, 255, 255)',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: '1rem',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}}>
|
|
||||||
Promo {(((initialPrice - promoPrice) / initialPrice) * 100).toFixed(0)}%
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<span style={{
|
|
||||||
marginLeft: '1rem',
|
|
||||||
marginRight: '0.5rem',
|
|
||||||
marginTop: '0.125rem'
|
|
||||||
}}>{promoPrice}</span>
|
|
||||||
<span style={{
|
|
||||||
marginTop: '0.125rem',
|
|
||||||
color: 'rgb(114, 114, 114)',
|
|
||||||
textDecoration: 'line-through'
|
|
||||||
}}>{initialPrice}</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
:
|
|
||||||
<>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<span style={{
|
|
||||||
marginRight: '0.5rem',
|
|
||||||
marginTop: '0.125rem'
|
|
||||||
}}>{initialPrice}</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!forInvoice &&
|
|
||||||
(!isBeingEdit && itemQty != 0 ? (
|
|
||||||
<div className={styles.itemQty}>
|
<div className={styles.itemQty}>
|
||||||
<svg
|
<div className={styles.qtyGroup}>
|
||||||
className={styles.plusNegative}
|
<button className={styles.qtyBtn} onClick={handleNegativeClick} aria-label="Kurangi">-</button>
|
||||||
onClick={handleNegativeClick}
|
{!blank && !isBeingEdit ? (
|
||||||
clipRule="evenodd"
|
<span className={styles.qtyVal}>{itemQty}</span>
|
||||||
fillRule="evenodd"
|
) : (
|
||||||
strokeLinejoin="round"
|
<input className={styles.itemQtyInput} value={itemQty} onChange={handleQtyChange} disabled={!blank && !isBeingEdit} />
|
||||||
strokeMiterlimit="2"
|
)}
|
||||||
viewBox="0 0 24 24"
|
<button className={styles.qtyBtn} onClick={handlePlusClick} aria-label="Tambah">+</button>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</div>
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="m12.002 2.005c5.518 0 9.998 4.48 9.998 9.997 0 5.518-4.48 9.998-9.998 9.998-5.517 0-9.997-4.48-9.997-9.998 0-5.517 4.48-9.997 9.997-9.997zm0 1.5c-4.69 0-8.497 3.807-8.497 8.497s3.807 8.498 8.497 8.498 8.498-3.808 8.498-8.498-3.808-8.497-8.498-8.497zm4.253 7.75h-8.5c-.414 0-.75.336-.75.75s.336.75.75.75h8.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75z"
|
|
||||||
fillRule="nonzero"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{!blank && !isBeingEdit ? (
|
|
||||||
<p className={styles.itemQtyValue}>{itemQty}</p>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
className={styles.itemQtyInput}
|
|
||||||
value={itemQty}
|
|
||||||
onChange={handleQtyChange}
|
|
||||||
disabled={!blank && !isBeingEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<svg
|
|
||||||
className={styles.plusNegative}
|
|
||||||
onClick={handlePlusClick}
|
|
||||||
clipRule="evenodd"
|
|
||||||
fillRule="evenodd"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeMiterlimit="2"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="m12.002 2c5.518 0 9.998 4.48 9.998 9.998 0 5.517-4.48 9.997-9.998 9.997-5.517 0-9.997-4.48-9.997-9.997 0-5.518 4.48-9.998 9.997-9.998zm0 1.5c-4.69 0-8.497 3.808-8.497 8.498s3.807 8.497 8.497 8.497 8.498-3.807 8.498-8.497-3.808-8.498-8.498-8.498zm-.747 7.75h-3.5c-.414 0-.75.336-.75.75s.336.75.75.75h3.5v3.5c0 .414.336.75.75.75s.75-.336.75-.75v-3.5h3.5c.414 0 .75-.336.75-.75s-.336-.75-.75-.75h-3.5v-3.5c0-.414-.336-.75-.75-.75s-.75.336-.75.75z"
|
|
||||||
fillRule="nonzero"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
) : !blank && !isBeingEdit ? (
|
) : !blank && !isBeingEdit ? (
|
||||||
<div className={styles.itemQty}>
|
<div className={styles.itemQty}>
|
||||||
<button
|
<button className={styles.addButton} onClick={handlePlusClick} disabled={!isAvailable}>
|
||||||
className={styles.addButton}
|
|
||||||
style={{ backgroundColor: !isAvailable ? "" : "inherit", border: `2px solid ${isAvailable ? 'inherit' : 'gray'}`, color: `${isAvailable ? '#a8c7a9' : 'gray'}` }}
|
|
||||||
onClick={handlePlusClick}
|
|
||||||
disabled={!isAvailable} // Optionally disable the button if not available
|
|
||||||
>
|
|
||||||
Pesan
|
Pesan
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.itemQty}>
|
<div className={styles.itemQty}>
|
||||||
<button
|
<button className={styles.addButton} style={{ backgroundColor: '#ffffff', color: 'var(--brand-sage, #6B8F71)', borderColor: 'var(--brand-sage, #6B8F71)', width: 150 }} onClick={isBeingEdit ? handleUpdate : handleCreate}>
|
||||||
className={styles.addButton}
|
{isBeingEdit ? 'Simpan' : 'Buat'}
|
||||||
style={{
|
|
||||||
backgroundColor: "white",
|
|
||||||
width: "150px",
|
|
||||||
color: '#a8c7a9'
|
|
||||||
}}
|
|
||||||
onClick={isBeingEdit ? handleUpdate : handleCreate}
|
|
||||||
>
|
|
||||||
{isBeingEdit ? "Simpan" : "Buat"}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
{forInvoice && (
|
{forInvoice && (
|
||||||
<p className={styles.itemPriceInvoice}>Rp {itemQty * (promoPrice > 0? promoPrice : itemPrice)}</p>
|
<p className={styles.itemPriceInvoice}>Rp {formatCurrency(itemQty * (promoPrice > 0 ? promoPrice : itemPrice))}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{forCart && (
|
{forCart && (
|
||||||
@@ -316,18 +210,11 @@ const Item = ({
|
|||||||
</button>
|
</button>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
{itemDescription && itemDescription != 'undefined' && itemDescription?.length &&
|
{itemDescription && itemDescription != 'undefined' && itemDescription?.length ? (
|
||||||
<div>
|
<div>
|
||||||
<p style={{
|
<p className={styles.desc} style={{ padding: '4px 6px', margin: 0 }}>{itemDescription}</p>
|
||||||
maxHeight: '34px',
|
|
||||||
display: '-webkit-box',
|
|
||||||
WebkitBoxOrient: 'vertical',
|
|
||||||
overflow: 'hidden',
|
|
||||||
WebkitLineClamp: 2,
|
|
||||||
textOverflow: 'ellipsis', color: '#5f5f5f', fontSize: '14px', padding: '5px', margin: 0
|
|
||||||
}}>{itemDescription}</p>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,19 +6,20 @@
|
|||||||
|
|
||||||
.item {
|
.item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-left: 5px;
|
width: 100%;
|
||||||
margin-top: 5px;
|
gap: 10px;
|
||||||
margin-bottom: 5px;
|
padding: 8px 10px;
|
||||||
color: rgba(88, 55, 50, 1);
|
margin: 6px 0;
|
||||||
font-size: 32px;
|
border: 1px solid #e3ece6;
|
||||||
box-sizing: border-box; /* Include padding and border in the element's total width */
|
border-radius: 12px;
|
||||||
width: 100%; /* Ensure the item does not exceed the parent's width */
|
background: var(--brand-sage-50, #F0F6F2);
|
||||||
overflow: hidden; /* Prevent internal overflow */
|
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
||||||
padding-top: 10px;
|
box-sizing: border-box;
|
||||||
margin-bottom: 5px;
|
transition: box-shadow 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
.item:hover { box-shadow: 0 4px 10px rgba(0,0,0,0.08); border-color: #d9e6de; }
|
||||||
|
|
||||||
.item:not(.itemInvoice) {
|
.item:not(.itemInvoice) {
|
||||||
/* border-top: 2px solid #00000017; */
|
/* border-top: 2px solid #00000017; */
|
||||||
@@ -50,9 +51,9 @@
|
|||||||
|
|
||||||
.imageContainer {
|
.imageContainer {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 26vw;
|
width: clamp(68px, 18vw, 96px);
|
||||||
height: 26vw;
|
height: clamp(68px, 18vw, 96px);
|
||||||
border-radius: 12px;
|
border-radius: 10px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,11 +85,15 @@
|
|||||||
.itemDetails {
|
.itemDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
margin-left: 10px;
|
align-items: stretch;
|
||||||
margin-right: 10px;
|
gap: 6px;
|
||||||
flex-grow: 1;
|
margin-left: 6px;
|
||||||
|
margin-right: 6px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.infoRow { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
||||||
|
|
||||||
.itemInvoiceDetails {
|
.itemInvoiceDetails {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -127,17 +132,7 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemPrice {
|
.itemPrice { display: none; }
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
width: calc(100% - 15px); /* Adjust the width to prevent overflow */
|
|
||||||
font-size: 3.3vw;
|
|
||||||
/* margin-bottom: 35px; */
|
|
||||||
margin-left: 5px;
|
|
||||||
color: #3a3a3a;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.itemPriceInvoice {
|
.itemPriceInvoice {
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
@@ -153,11 +148,9 @@
|
|||||||
.itemQty {
|
.itemQty {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 0.9rem;
|
justify-content: flex-end;
|
||||||
margin-left: 5px;
|
gap: 8px;
|
||||||
color: #a8c7a9;
|
min-height: 32px;
|
||||||
fill: #a8c7a9;
|
|
||||||
height: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemQtyValue {
|
.itemQtyValue {
|
||||||
@@ -183,19 +176,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.addButton {
|
.addButton {
|
||||||
background-color: #ffffff;
|
background-color: var(--brand-sage, #6B8F71);
|
||||||
border: 2px solid #a8c7a9;
|
border: 1px solid var(--brand-sage, #6B8F71);
|
||||||
/* border: none; */
|
color: #ffffff;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 14px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 87px;
|
min-width: 84px;
|
||||||
height: 32px;
|
height: 34px;
|
||||||
margin-left: 5px;
|
padding: 0 14px;
|
||||||
margin-top: 5px;
|
border-radius: 10px; /* square rounded corner */
|
||||||
border-radius: 20px;
|
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
|
.addButton:hover { background-color: var(--brand-sage-hover, #7FAE7D); border-color: var(--brand-sage-hover, #7FAE7D); }
|
||||||
|
.addButton:disabled { background-color: var(--brand-sage-muted, #CFD8D3); border-color: var(--brand-sage-muted, #CFD8D3); cursor: default; }
|
||||||
.grayscale {
|
.grayscale {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
@@ -204,9 +199,8 @@
|
|||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
.plusNegative {
|
.plusNegative {
|
||||||
width: 35px;
|
width: 30px;
|
||||||
height: 35px;
|
height: 30px;
|
||||||
margin: 2.5px 0 -0.5px 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.plusNegative2 {
|
.plusNegative2 {
|
||||||
@@ -224,6 +218,91 @@
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* New elements for clean cafe item card */
|
||||||
|
.title {
|
||||||
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #2d2d2d;
|
||||||
|
margin: 0 0 2px 0;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
text-transform: capitalize;
|
||||||
|
text-align: left;
|
||||||
|
flex: 1; min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive type scale for title and price */
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
.title { font-size: 17px; }
|
||||||
|
.priceNow { font-size: 15px; }
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.title { font-size: 18px; }
|
||||||
|
.priceNow { font-size: 16px; }
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
color: #5f5f5f;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.25;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
.priceRow { display: inline-flex; align-items: center; gap: 8px; }
|
||||||
|
.promoBadge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(to right, #e52535, #fe6d78);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.priceNow {
|
||||||
|
color: #1c1d1d;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.priceOld {
|
||||||
|
color: #727272;
|
||||||
|
font-size: 12px;
|
||||||
|
text-decoration: line-through;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.qtyGroup {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
border-radius: 10px; /* square rounded corners */
|
||||||
|
height: 32px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.qtyBtn {
|
||||||
|
width: 34px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--brand-sage, #6B8F71);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.qtyBtn:hover { background: var(--brand-sage-50, #F0F6F2); }
|
||||||
|
.qtyVal {
|
||||||
|
min-width: 28px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
.itemInvoice .itemDetails {
|
.itemInvoice .itemDetails {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import ItemType from "./ItemType.js";
|
import ItemType from "./ItemType.js";
|
||||||
import { createItemType, updateItemDeletionStatus } from "../helpers/itemHelper.js";
|
import { createItemType, updateItemDeletionStatus } from "../helpers/itemHelper.js";
|
||||||
import ItemConfig from "./ItemConfig.js"
|
import ItemConfig from "./ItemConfig.js"
|
||||||
|
import { ArrowUp, ArrowDown, Pencil } from 'lucide-react';
|
||||||
|
|
||||||
const ItemLister = ({
|
const ItemLister = ({
|
||||||
index,
|
index,
|
||||||
@@ -618,87 +619,31 @@ const ItemLister = ({
|
|||||||
disabled={!isEdit}
|
disabled={!isEdit}
|
||||||
/>
|
/>
|
||||||
{isEditMode && !isEdit && (
|
{isEditMode && !isEdit && (
|
||||||
<>
|
<div className={styles.titleActions}>
|
||||||
<div
|
<button
|
||||||
style={{
|
className={styles.iconBtn}
|
||||||
width: '32px',
|
onClick={() => index === 0 ? null : moveItemTypeUp(itemTypeId)}
|
||||||
height: '32px', // Add a height to the div
|
disabled={index === 0}
|
||||||
display: 'flex', // Use flexbox
|
aria-label="Naikkan kategori"
|
||||||
justifyContent: 'center', // Center horizontally
|
|
||||||
alignItems: 'center', // Center vertically
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
onClick={index == 0 ? null : () => moveItemTypeUp(itemTypeId)} // Move onClick here for the whole div
|
|
||||||
>
|
>
|
||||||
<svg
|
<ArrowUp size={18} />
|
||||||
viewBox="0 0 16 16"
|
</button>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<button
|
||||||
fill="#000000"
|
className={styles.iconBtn}
|
||||||
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
onClick={() => index === indexTotal - 1 ? null : moveItemTypeDown(itemTypeId)}
|
||||||
>
|
disabled={index === indexTotal - 1}
|
||||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
aria-label="Turunkan kategori"
|
||||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
|
||||||
<g id="SVGRepo_iconCarrier">
|
|
||||||
<path d="m 1 11 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 l 6 -6 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -5.292969 -5.292969 l -5.292969 5.292969 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={index === 0 ? "gray" : "#2e3436"}></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '32px',
|
|
||||||
height: '32px', // Add a height to the div
|
|
||||||
display: 'flex', // Use flexbox
|
|
||||||
justifyContent: 'center', // Center horizontally
|
|
||||||
alignItems: 'center', // Center vertically
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
onClick={index == indexTotal - 1 ? null : () => moveItemTypeDown(itemTypeId)} // Move onClick here for the whole div
|
|
||||||
>
|
>
|
||||||
<svg
|
<ArrowDown size={18} />
|
||||||
viewBox="0 0 16 16"
|
</button>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<button
|
||||||
fill="#000000"
|
className={styles.iconBtn}
|
||||||
style={{ width: '100%', height: '100%' }} // Ensure SVG fits the div
|
onClick={toggleEditTypeItem}
|
||||||
>
|
aria-label="Edit kategori"
|
||||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
|
||||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
|
||||||
<g id="SVGRepo_iconCarrier">
|
|
||||||
<path d="m 1 5 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 5.292969 5.292969 l 5.292969 -5.292969 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 l -6 -6 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0" fill={index === indexTotal - 1 ? "gray" : "#2e3436"}></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '32px',
|
|
||||||
height: '32px', // Add a height to the div
|
|
||||||
display: 'flex', // Use flexbox
|
|
||||||
justifyContent: 'center', // Center horizontally
|
|
||||||
alignItems: 'center', // Center vertically
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
onClick={toggleEditTypeItem} // Move onClick here for the whole div
|
|
||||||
>
|
>
|
||||||
<svg
|
<Pencil size={18} />
|
||||||
fill="#000000"
|
</button>
|
||||||
viewBox="0 0 32 32"
|
</div>
|
||||||
style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 2 }}
|
|
||||||
version="1.1"
|
|
||||||
xmlSpace="preserve"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsSerif="http://www.serif.com/"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
>
|
|
||||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
|
||||||
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
|
|
||||||
<g id="SVGRepo_iconCarrier">
|
|
||||||
<path d="M12.965,5.462c0,-0 -2.584,0.004 -4.979,0.008c-3.034,0.006 -5.49,2.467 -5.49,5.5l0,13.03c0,1.459 0.579,2.858 1.611,3.889c1.031,1.032 2.43,1.611 3.889,1.611l13.003,0c3.038,-0 5.5,-2.462 5.5,-5.5c0,-2.405 0,-5.004 0,-5.004c0,-0.828 -0.672,-1.5 -1.5,-1.5c-0.827,-0 -1.5,0.672 -1.5,1.5l0,5.004c0,1.381 -1.119,2.5 -2.5,2.5l-13.003,0c-0.663,-0 -1.299,-0.263 -1.768,-0.732c-0.469,-0.469 -0.732,-1.105 -0.732,-1.768l0,-13.03c0,-1.379 1.117,-2.497 2.496,-2.5c2.394,-0.004 4.979,-0.008 4.979,-0.008c0.828,-0.002 1.498,-0.675 1.497,-1.503c-0.001,-0.828 -0.675,-1.499 -1.503,-1.497Z"></path>
|
|
||||||
<path d="M20.046,6.411l-6.845,6.846c-0.137,0.137 -0.232,0.311 -0.271,0.501l-1.081,5.152c-0.069,0.329 0.032,0.671 0.268,0.909c0.237,0.239 0.577,0.343 0.907,0.277l5.194,-1.038c0.193,-0.039 0.371,-0.134 0.511,-0.274l6.845,-6.845l-5.528,-5.528Zm1.415,-1.414l5.527,5.528l1.112,-1.111c1.526,-1.527 1.526,-4.001 -0,-5.527c-0.001,-0 -0.001,-0.001 -0.001,-0.001c-1.527,-1.526 -4.001,-1.526 -5.527,-0l-1.111,1.111Z"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,33 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.titleActions {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconBtn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #2d2d2d;
|
||||||
|
border-radius: 8px; /* square rounded */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.iconBtn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.iconBtn:hover:not(:disabled) {
|
||||||
|
background: var(--brand-sage-50, #F0F6F2);
|
||||||
|
border-color: var(--brand-sage, #6B8F71);
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
@@ -224,4 +251,4 @@
|
|||||||
font-size: 6vw;
|
font-size: 6vw;
|
||||||
color: black;
|
color: black;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
import styles from "./ItemType.module.css";
|
import styles from "./ItemType.module.css";
|
||||||
|
import { Coffee, CupSoda, CakeSlice, Utensils, Grid2X2, Plus } from 'lucide-react';
|
||||||
|
|
||||||
export default function ItemType({
|
export default function ItemType({
|
||||||
onClick,
|
onClick,
|
||||||
@@ -57,6 +58,15 @@ export default function ItemType({
|
|||||||
onCreate(namee, selectedImage);
|
onCreate(namee, selectedImage);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatName = (val) => {
|
||||||
|
if (!val || typeof val !== 'string') return val;
|
||||||
|
return val
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconImageUrl = imageUrl === 'uploads/assets/All.png' ? 'icon:all' : imageUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
@@ -72,27 +82,25 @@ export default function ItemType({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={
|
onClick={
|
||||||
rectangular ? (blank ? null : () => onClick(imageUrl)) : onClick
|
rectangular ? (blank ? null : () => onClick(iconImageUrl)) : onClick
|
||||||
}
|
}
|
||||||
className={styles["item-type-rect"]}
|
className={styles["item-type-rect"]}
|
||||||
style={{
|
style={{
|
||||||
top: selected ? "-10px" : "initial",
|
// Remove lift-up effect; only color changes when selected
|
||||||
|
backgroundColor: selected ? 'var(--brand-sage, #6B8F71)' : '#ffffff',
|
||||||
|
border: selected ? '1px solid var(--brand-sage, #6B8F71)' : '1px solid #e6e6e6',
|
||||||
|
color: selected ? '#ffffff' : '#4a6b5a'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{imageUrl != 'uploads/assets/All.png' ?
|
{iconImageUrl === 'uploads/assets/All.png' ? (
|
||||||
<img
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
src={previewUrl}
|
|
||||||
alt={namee}
|
|
||||||
className={styles["item-type-image"]}
|
|
||||||
/>
|
|
||||||
:<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="100%" height="100%" viewBox="0 0 800.000000 800.000000"
|
width="100%" height="100%" viewBox="0 0 800.000000 800.000000"
|
||||||
preserveAspectRatio="xMidYMid meet">
|
preserveAspectRatio="xMidYMid meet">
|
||||||
<metadata>
|
<metadata>
|
||||||
Created by potrace 1.16, written by Peter Selinger 2001-2019
|
Created by potrace 1.16, written by Peter Selinger 2001-2019
|
||||||
</metadata>
|
</metadata>
|
||||||
<g transform="translate(0.000000,800.000000) scale(0.100000,-0.100000)"
|
<g transform="translate(0.000000,800.000000) scale(0.100000,-0.100000)"
|
||||||
fill="#000000" stroke="none">
|
fill="currentColor" stroke="none">
|
||||||
<path d="M3708 7165 c-3 -4 -44 -10 -90 -15 -266 -28 -530 -91 -753 -180 -11
|
<path d="M3708 7165 c-3 -4 -44 -10 -90 -15 -266 -28 -530 -91 -753 -180 -11
|
||||||
-4 -42 -16 -70 -26 -27 -9 -129 -57 -225 -106 -186 -94 -188 -95 -262 -145
|
-4 -42 -16 -70 -26 -27 -9 -129 -57 -225 -106 -186 -94 -188 -95 -262 -145
|
||||||
-26 -18 -52 -33 -58 -33 -5 0 -24 -13 -42 -28 -18 -16 -53 -43 -78 -61 -124
|
-26 -18 -52 -33 -58 -33 -5 0 -24 -13 -42 -28 -18 -16 -53 -43 -78 -61 -124
|
||||||
@@ -138,7 +146,17 @@ c261 0 329 -3 352 -14z m1237 -2 c52 -35 54 -49 54 -379 0 -348 -2 -360 -69
|
|||||||
58 40 59 387 60 178 0 328 -4 342 -10z"/>
|
58 40 59 387 60 178 0 328 -4 342 -10z"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
) : (iconImageUrl && typeof iconImageUrl === 'string' && iconImageUrl.startsWith('icon:')) ? (
|
||||||
|
<div style={{width:'100%',height:'100%',display:'flex',alignItems:'center',justifyContent:'center'}}>
|
||||||
|
<LucideCategoryIcon name={namee} iconKey={(iconImageUrl || '').split(':')[1]} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={previewUrl}
|
||||||
|
alt={namee}
|
||||||
|
className={styles["item-type-image"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{blank && rectangular && (
|
{blank && rectangular && (
|
||||||
<div className={styles["item-type-image-container"]}>
|
<div className={styles["item-type-image-container"]}>
|
||||||
<input
|
<input
|
||||||
@@ -155,15 +173,49 @@ c261 0 329 -3 352 -14z m1237 -2 c52 -35 54 -49 54 -379 0 -348 -2 -360 -69
|
|||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className={`${styles["item-type-name"]} ${styles.noborder}`}
|
className={`${styles["item-type-name"]} ${styles.noborder}`}
|
||||||
value={namee}
|
value={formatName(namee)}
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
style={{
|
style={{
|
||||||
top: selected ? "-5px" : "initial",
|
top: 'initial',
|
||||||
borderBottom: selected ? "1px solid #000" : "none",
|
borderBottom: 'none',
|
||||||
|
color: selected ? '#2d2d2d' : '#333',
|
||||||
|
textTransform: 'capitalize'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LucideCategoryIcon({ name, iconKey }) {
|
||||||
|
const key = pickIconKey(name, iconKey);
|
||||||
|
const size = '56%';
|
||||||
|
switch (key) {
|
||||||
|
case 'coffee':
|
||||||
|
return <Coffee color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
case 'drink':
|
||||||
|
return <CupSoda color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
case 'dessert':
|
||||||
|
return <CakeSlice color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
case 'food':
|
||||||
|
return <Utensils color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
case 'all':
|
||||||
|
return <Grid2X2 color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
case 'plus':
|
||||||
|
return <Plus color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
default:
|
||||||
|
return <Utensils color={'currentColor'} size={size} strokeWidth={2} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickIconKey(name, iconKey) {
|
||||||
|
const n = (name || '').toLowerCase();
|
||||||
|
if (iconKey === 'plus') return 'plus';
|
||||||
|
if (iconKey === 'all') return 'all';
|
||||||
|
if (/(kopi|coffee|espresso|latte|americano|kapal|brew)/.test(n)) return 'coffee';
|
||||||
|
if (/(teh|tea|drink|minum|soda|juice|jus|milk|susu|lemon)/.test(n)) return 'drink';
|
||||||
|
if (/(dessert|cake|kue|manis|ice|es krim|ice-cream)/.test(n)) return 'dessert';
|
||||||
|
if (/(food|makan|snack|cemilan|nasi|mie|noodle|soup|sup|ayam|daging|ikan|roti|sandwich|burger|pizza)/.test(n)) return 'food';
|
||||||
|
return 'food';
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.item-type {
|
.item-type {
|
||||||
width: calc(25vw - 20px);
|
width: auto;
|
||||||
height: calc(30vw - 20px);
|
height: auto;
|
||||||
margin: 1px 10px 0px;
|
margin: 0 4px; /* tighter spacing between tiles */
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -34,24 +34,33 @@
|
|||||||
}
|
}
|
||||||
.item-type-rect {
|
.item-type-rect {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 13vw;
|
height: clamp(48px, 9vw, 80px);
|
||||||
width: 13vw;
|
width: clamp(48px, 9vw, 80px);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 15px;
|
border-radius: 12px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-type-rect:hover {
|
||||||
|
background-color: var(--brand-sage-100, #E9F3ED);
|
||||||
|
border-color: var(--brand-sage, #6B8F71);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-type-name {
|
.item-type-name {
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
color: #333;
|
color: #333;
|
||||||
width: calc(25vw - 30px);
|
width: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
position: relative; /* Needed for positioning the button */
|
position: relative;
|
||||||
|
margin-top: 6px; /* keep label spacing constant; avoid jumping */
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-type-image {
|
.item-type-image {
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
|
/* New clean, intuitive category bar */
|
||||||
.item-type-lister {
|
.item-type-lister {
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 3px 0px;
|
padding: 2px 0px; /* tighter top/bottom padding */
|
||||||
margin-bottom: -5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 8px 12px 4px;
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
.category-bar::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
/* Legacy horizontal tile list container (used for tile UI) */
|
||||||
.item-type-list {
|
.item-type-list {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
-ms-overflow-style: none; /* IE and Edge */
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
@@ -13,11 +25,44 @@
|
|||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-type {
|
.category-chip {
|
||||||
display: inline-block;
|
flex: 0 0 auto;
|
||||||
margin-right: 20px;
|
display: inline-flex;
|
||||||
/* Space between items */
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #2d2d2d;
|
||||||
|
font-family: "Plus Jakarta Sans", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
.category-chip:hover { border-color: #d0d0d0; }
|
||||||
|
.category-chip.selected {
|
||||||
|
background: #73a585;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: #73a585;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-chip .chip-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-item-chip {
|
||||||
|
background: #f4f7f5;
|
||||||
|
border-color: #dfe7e2;
|
||||||
|
color: #4a6b5a;
|
||||||
|
}
|
||||||
|
.add-item-chip:hover { background: #eaf1ed; }
|
||||||
.rect-creator {
|
.rect-creator {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -55,13 +100,4 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
align-self: center; /* Center the button horizontally */
|
align-self: center; /* Center the button horizontally */
|
||||||
}
|
}
|
||||||
.item-type-name {
|
/* Legacy styles kept for ItemType grid if needed elsewhere */
|
||||||
font-family: "Plus Jakarta Sans", sans-serif;
|
|
||||||
font-style: normal;
|
|
||||||
height: 20vw;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: black;
|
|
||||||
text-transform: capitalize;
|
|
||||||
z-index: 301;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect } from "react";
|
|||||||
import smoothScroll from "smooth-scroll-into-view-if-needed";
|
import smoothScroll from "smooth-scroll-into-view-if-needed";
|
||||||
import "./ItemTypeLister.css";
|
import "./ItemTypeLister.css";
|
||||||
import ItemType from "./ItemType";
|
import ItemType from "./ItemType";
|
||||||
import { createItem, createItemType } from "../helpers/itemHelper.js";
|
import { createItem } from "../helpers/itemHelper.js";
|
||||||
import { getImageUrl } from "../helpers/itemHelper";
|
import { getImageUrl } from "../helpers/itemHelper";
|
||||||
import ItemLister from "./ItemLister";
|
import ItemLister from "./ItemLister";
|
||||||
|
|
||||||
@@ -22,24 +22,6 @@ const ItemTypeLister = ({
|
|||||||
const newItemDivRef = useRef(null);
|
const newItemDivRef = useRef(null);
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
|
|
||||||
const [itemTypeName, setItemTypeName] = useState("");
|
|
||||||
const handleCreateItem = (name, price, selectedImage, previewUrl, description, promoPrice) => {
|
|
||||||
console.log(previewUrl);
|
|
||||||
const newItem = {
|
|
||||||
itemId: items.length + 1,
|
|
||||||
name,
|
|
||||||
price,
|
|
||||||
selectedImage,
|
|
||||||
image: previewUrl,
|
|
||||||
availability: true,
|
|
||||||
description,
|
|
||||||
promoPrice
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update the items state with the new item
|
|
||||||
setItems((prevItems) => [...prevItems, newItem]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Effect to handle changes to isAddingNewItem
|
// Effect to handle changes to isAddingNewItem
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isAddingNewItem && newItemDivRef.current) {
|
if (isAddingNewItem && newItemDivRef.current) {
|
||||||
@@ -67,90 +49,63 @@ const ItemTypeLister = ({
|
|||||||
document.body.style.overflow = !isAddingNewItem ? "hidden" : "auto";
|
document.body.style.overflow = !isAddingNewItem ? "hidden" : "auto";
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleCreate(name, selectedImage) {
|
// Removed legacy image upload logic used by the old tile view
|
||||||
createItemType(shopId, name, selectedImage);
|
|
||||||
}
|
const canManage = user && (user.user_id == shopOwnerId || user.cafeId == shopId);
|
||||||
|
|
||||||
const [selectedImage, setSelectedImage] = useState(null);
|
const formatName = (name) => {
|
||||||
const [previewUrl, setPreviewUrl] = useState("");
|
if (!name) return name;
|
||||||
const [imageUrl, setImaguUrl] = useState("");
|
return name
|
||||||
|
.toLowerCase()
|
||||||
useEffect(() => {
|
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
// if (selectedImage) {
|
|
||||||
// const reader = new FileReader();
|
|
||||||
// reader.onloadend = () => {
|
|
||||||
// setPreviewUrl(reader.result);
|
|
||||||
// };
|
|
||||||
// reader.readAsDataURL(selectedImage);
|
|
||||||
// } else {
|
|
||||||
// setPreviewUrl(getImageUrl(imageUrl));
|
|
||||||
setPreviewUrl(selectedImage);
|
|
||||||
// }
|
|
||||||
}, [selectedImage, imageUrl]);
|
|
||||||
const handleImageChange = (e) => {
|
|
||||||
setSelectedImage(e);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="item-type-lister" style={{ overflowX: isAddingNewItem ? 'hidden' : 'auto' }}>
|
||||||
className="item-type-lister"
|
<div ref={newItemDivRef} className="item-type-list" style={{ display: 'inline-flex' }}>
|
||||||
style={{ overflowX: isAddingNewItem ? "hidden" : "" }}
|
{isEditMode && !isAddingNewItem && canManage && (
|
||||||
>
|
<ItemType
|
||||||
<div
|
onClick={toggleAddNewItem}
|
||||||
ref={newItemDivRef}
|
name={"buat baru"}
|
||||||
className="item-type-list"
|
imageUrl={"icon:plus"}
|
||||||
style={{ display: isAddingNewItem ? "inline-flex" : "inline-flex" }}
|
/>
|
||||||
>
|
)}
|
||||||
{isEditMode &&
|
|
||||||
!isAddingNewItem &&
|
{canManage && isAddingNewItem && (
|
||||||
user && (
|
<ItemLister
|
||||||
user.user_id == shopOwnerId || user.cafeId == shopId) && (
|
shopId={shopId}
|
||||||
<ItemType
|
shopOwnerId={shopOwnerId}
|
||||||
onClick={toggleAddNewItem}
|
user={user}
|
||||||
name={"buat baru"}
|
typeName={""}
|
||||||
imageUrl={getImageUrl("uploads/assets/addnew.png")}
|
setShopItems={setShopItems}
|
||||||
/>
|
itemList={items}
|
||||||
)}
|
isEditMode={true}
|
||||||
{user &&(
|
handleCreateItem={(itemTypeId, name, price, selectedImage, description, promoPrice) => createItem(shopId, name, price, selectedImage, itemTypeId, description, promoPrice)}
|
||||||
user.user_id == shopOwnerId || user.cafeId == shopId) &&
|
beingEditedType={beingEditedType}
|
||||||
isAddingNewItem && (
|
setBeingEditedType={setBeingEditedType}
|
||||||
<>
|
alwaysEdit={true}
|
||||||
<ItemLister
|
handleUnEdit={toggleAddNewItem}
|
||||||
shopId={shopId}
|
/>
|
||||||
shopOwnerId={shopOwnerId}
|
)}
|
||||||
user={user}
|
|
||||||
typeName={""}
|
|
||||||
setShopItems={setShopItems}
|
|
||||||
itemList={items}
|
|
||||||
isEditMode={true}
|
|
||||||
handleCreateItem={(itemTypeId, name, price, selectedImage, description, promoPrice) => createItem(shopId, name, price, selectedImage, itemTypeId, description, promoPrice)}
|
|
||||||
beingEditedType={beingEditedType}
|
|
||||||
setBeingEditedType={setBeingEditedType}
|
|
||||||
alwaysEdit={true}
|
|
||||||
handleUnEdit={toggleAddNewItem}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{itemTypes && itemTypes.length > 0 && (
|
{itemTypes && itemTypes.length > 0 && (
|
||||||
<ItemType
|
<ItemType
|
||||||
name={"semua"}
|
name={"semua"}
|
||||||
onClick={() => onFilterChange(0)}
|
onClick={() => onFilterChange(0)}
|
||||||
imageUrl={"uploads/assets/All.png"}
|
imageUrl={"icon:all"}
|
||||||
|
selected={filterId === 0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{itemTypes &&
|
|
||||||
itemTypes.map(
|
{itemTypes && itemTypes.map((itemType) => (
|
||||||
(itemType) =>
|
<ItemType
|
||||||
(
|
key={itemType.itemTypeId}
|
||||||
itemType.itemList.length > 0 || (user && (user.user_id == shopOwnerId || user.cafeId == shopId))) && (
|
name={itemType.name}
|
||||||
<ItemType
|
imageUrl={"icon:category"}
|
||||||
key={itemType.itemTypeId}
|
onClick={() => onFilterChange(itemType.itemTypeId)}
|
||||||
name={itemType.name}
|
selected={filterId === itemType.itemTypeId}
|
||||||
imageUrl={getImageUrl(itemType.image)}
|
/>
|
||||||
onClick={() => onFilterChange(itemType.itemTypeId)}
|
))}
|
||||||
selected={filterId === itemType.itemTypeId}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ function CafePage({
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const token = searchParams.get("token");
|
const token = searchParams.get("token");
|
||||||
const { shopIdentifier, tableCode } = useParams();
|
const { shopIdentifier, tableCode } = useParams();
|
||||||
|
// Send params to parent immediately (original behavior)
|
||||||
sendParam({ shopIdentifier, tableCode });
|
sendParam({ shopIdentifier, tableCode });
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -319,7 +320,7 @@ function CafePage({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!isEditMode && (user.username || cartItemsLength > 0) &&
|
{!isEditMode && (user.username || cartItemsLength > 0) &&
|
||||||
<div style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
<div className="StickyCartBar" style={{ marginTop: '10px', height: '40px', position: 'sticky', bottom: '40px', display: 'flex', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
|
||||||
{(lastTransaction != null || cartItemsLength > 0) &&
|
{(lastTransaction != null || cartItemsLength > 0) &&
|
||||||
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}>
|
<div onClick={goToCart} style={{ backgroundColor: '#73a585', width: user.username ? '55vw' : '70vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'space-between', padding: '0 20px' }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div>
|
<div style={{ display: 'flex', flexWrap: 'wrap', alignContent: 'center' }}>{lastTransaction != null && '+'}{cartItemsLength} item</div>
|
||||||
@@ -338,7 +339,7 @@ function CafePage({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{user.username &&
|
{user.username &&
|
||||||
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: lastTransaction != null || cartItemsLength > 0 ? '6px' : '0px' }}>
|
<div onClick={goToTransactions} style={{ backgroundColor: '#73a585', width: '15vw', height: '40px', borderRadius: '30px', display: 'flex', justifyContent: 'center', marginLeft: (lastTransaction != null || cartItemsLength > 0) ? '6px' : '0px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '38px', marginRight: '5px' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', marginLeft: '5px', width: '20px' }}>
|
||||||
<svg viewBox="0 0 512 512">
|
<svg viewBox="0 0 512 512">
|
||||||
|
|||||||
Reference in New Issue
Block a user