diff --git a/package.json b/package.json index 9586848c11b707d3a3f13324a043ab48d287ad0f..d6d5a0e7c415b31b2b42a34d02a248a1aaa18b6e 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,20 @@ "@emotion/babel-preset-css-prop": "^10.0.27", "@emotion/core": "^10.0.28", "@emotion/styled": "^10.0.27", - "@material-ui/core": "^4.9.9", + "@material-ui/core": "^4.9.11", "@material-ui/icons": "^4.9.1", "@reach/router": "^1.3.3", "bootstrap": "^4.4.1", - "jquery": "^3.4.1", + "jquery": "^3.5.0", + "moment": "^2.24.0", + "moment-timezone": "^0.5.28", "popper.js": "^1.16.1", "react": "^16.13.1", "react-bootstrap": "^1.0.0", "react-dom": "^16.13.1", - "react-hook-form": "^5.3.1", + "react-hook-form": "^5.5.2", + "react-moment": "^0.9.7", + "react-number-format": "^4.4.1", "react-promise-tracker": "^2.1.0" }, "scripts": { @@ -46,7 +50,7 @@ "@babel/preset-env": "^7.9.5", "@babel/preset-react": "^7.9.4", "@testing-library/dom": "^7.2.1", - "@testing-library/react": "^10.0.2", + "@testing-library/react": "^10.0.3", "@types/jest": "^25.2.1", "babel-eslint": "^10.1.0", "babel-plugin-emotion": "^10.0.33", @@ -60,14 +64,14 @@ "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^2.5.1", - "jest": "^25.3.0", + "jest": "^25.4.0", "jest-environment-enzyme": "^7.1.2", "jest-enzyme": "^7.1.2", "jest-fetch-mock": "^3.0.3", "jest-sonar-reporter": "^2.0.0", "mutationobserver-shim": "^0.3.5", "parcel-bundler": "^1.12.4", - "prettier": "^2.0.4", + "prettier": "^2.0.5", "regenerator-runtime": "^0.13.5" }, "jest": { diff --git a/src/__test__/DetailPengguna.test.js b/src/__test__/DetailPengguna.test.js index 500eb0dc5d2fdd1be71dd8400c27b07404bb4bd4..fe6bae921a659cf6c7a89fab42542cf6eeadd06a 100644 --- a/src/__test__/DetailPengguna.test.js +++ b/src/__test__/DetailPengguna.test.js @@ -10,20 +10,67 @@ beforeEach(() => { afterEach(cleanup); test("Test detail pengguna renders", async () => { - fetch.mockResponseOnce( - JSON.stringify({ - id: "663392ac-1dd6-462b-9301-a19c1287cefd", - username: "dummyuser", - full_name: "Dummy User", - phone_number: "+6285212345678", - address: "Jl. Dummy No.1", - neighborhood: "000", - hamlet: "000", - urban_village: "Dummy Urban Village", - sub_district: "Dummy Sub-District", - profile_picture: null, - }) - ); + fetch + .once( + JSON.stringify({ + count: 17, + next: + "https://industripilar-staging.herokuapp.com/transactions/?page=2", + previous: null, + results: [ + { + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: null, + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }, + ], + }) + ) + .once( + JSON.stringify({ + id: "663392ac-1dd6-462b-9301-a19c1287cefd", + username: "dummyuser", + full_name: "Dummy User", + phone_number: "+6285212345678", + address: "Jl. Dummy No.1", + neighborhood: "000", + hamlet: "000", + urban_village: "Dummy Urban Village", + sub_district: "Dummy Sub-District", + profile_picture: null, + }) + ); const { getByTestId } = render( <AuthContext.Provider value={{ profile: { token: "BEBAS" } }}> <DetailPengguna /> @@ -41,14 +88,61 @@ test("Test detail pengguna renders", async () => { }); test("Test mock return error", async () => { - fetch.mockReject(new Error("fake error message")); + fetch + .once( + JSON.stringify({ + count: 17, + next: + "https://industripilar-staging.herokuapp.com/transactions/?page=2", + previous: null, + results: [ + { + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: null, + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }, + ], + }) + ) + .mockReject(new Error("fake error message")); const { getByTestId } = render( <AuthContext.Provider value={{ profile: { token: "BEBAS" } }}> <DetailPengguna /> </AuthContext.Provider> ); - const page = getByTestId("page"); + const page = getByTestId("page-profile"); await waitFor(() => expect(page.textContent).toContain("Error !, Please relogin..") ); diff --git a/src/__test__/produk/EditProduk.test.js b/src/__test__/produk/EditProduk.test.js index 19f2f49909d5cd3eaedb8531846864a2798c86a3..4fedd2457afff30163a49c8c812ba88868254630 100644 --- a/src/__test__/produk/EditProduk.test.js +++ b/src/__test__/produk/EditProduk.test.js @@ -70,8 +70,7 @@ test("Test edit produk renders error", async () => { { id: "f0c08b4f-7421-4298-89e4-3d4a40ef15b4", name: "Baju", - image: - "https://industripilar-api-staging.s3.amazonaws.com/media/uploads/categories/download.png", + image: null, }, { id: "0664247c-d9ea-4e56-bb02-4b8463f9e14c", @@ -134,6 +133,8 @@ test("Test edit produk renders error", async () => { await fireEvent.submit(getByTestId("submit-produk")); }); const produk = getByTestId("edit-produk"); - expect(produk.textContent).toContain("Error loading form !, Please relogin"); + expect(produk.textContent).toContain( + "Error loading form !, Please relogin.." + ); expect(fetch.mock.calls.length).toEqual(5); }); diff --git a/src/__test__/produk/ListProduk.test.js b/src/__test__/produk/ListProduk.test.js index 35531cdde4fb6a81511958e3ee6f0620f76f489a..5a5094b884e594a6310d51361aa8f7ee66659e35 100644 --- a/src/__test__/produk/ListProduk.test.js +++ b/src/__test__/produk/ListProduk.test.js @@ -1,8 +1,8 @@ import { cleanup, render } from "@testing-library/react"; import AuthContext from "../../utils/contex"; import React from "react"; -import ListSubkategori from "../../page/produk/ListProduk"; import { waitFor } from "@testing-library/dom"; +import ListProduk from "../../page/produk/ListProduk"; beforeEach(() => { fetch.resetMocks(); @@ -12,32 +12,46 @@ afterEach(cleanup); test(" Test List produk", async () => { fetch.mockResponseOnce( JSON.stringify({ - count: 2, - next: null, + count: 23, + next: "https://industripilar-staging.herokuapp.com/products/?page=2", previous: null, results: [ { - id: "1fac049f-592c-4c15-afe6-9e05a2ce1540", - name: "Kue Nastar", - price: 10000, - stock: 10, + id: "3d403cd3-e356-4c15-9a86-8843333e2778", + code: "5VK6TY", + name: "a", + category: "f0c08b4f-7421-4298-89e4-3d4a40ef15b4", + category_name: "Baju", + subcategory: "626aa022-50a7-4d3a-b658-79cb0f059b03", + subcategory_name: "Baju Tidur hehe", + description: "celana", + price: "50000.00", + stock: 9, + image: null, }, { - id: "626aa022-50a7-4d3a-b658-79cb0f059b03", - name: "Kastangel", - price: 40000, - stock: 4, + id: "9a0bccaa-70f6-48a8-89fc-5c5994684729", + code: "4QKSBC", + name: "Piyama", + category: "f0c08b4f-7421-4298-89e4-3d4a40ef15b4", + category_name: "Baju", + subcategory: "626aa022-50a7-4d3a-b658-79cb0f059b03", + subcategory_name: "Baju Tidur hehe", + description: "piyama", + price: "50000.00", + stock: 14, + image: null, }, ], }) ); - const { getByTestId, getByText } = render( + const { getByTestId } = render( <AuthContext.Provider value={{ profile: { token: "tester" } }}> - <ListSubkategori /> + <ListProduk /> </AuthContext.Provider> ); await waitFor(() => getByTestId("tableList")); - const data = getByText("Kue Nastar"); - expect(data.textContent).toContain("Kue Nastar"); + const data = getByTestId("tableList"); + expect(data.textContent).toContain("Piyama"); expect(fetch.mock.calls.length).toEqual(1); }); diff --git a/src/__test__/produk/TambahProduk.test.js b/src/__test__/produk/TambahProduk.test.js index 68352fb781eb3d49b36b97e3ee36ad23db66ee8a..699c4041fc362c8eb655e8b242f39fa8a9b300af 100644 --- a/src/__test__/produk/TambahProduk.test.js +++ b/src/__test__/produk/TambahProduk.test.js @@ -18,8 +18,7 @@ test("Test tambah produk renders", async () => { { id: "f0c08b4f-7421-4298-89e4-3d4a40ef15b4", name: "Baju", - image: - "https://industripilar-api-staging.s3.amazonaws.com/media/uploads/categories/download.png", + image: null, }, { id: "0664247c-d9ea-4e56-bb02-4b8463f9e14c", @@ -102,8 +101,7 @@ test("Test tambah produk form required", async () => { { id: "f0c08b4f-7421-4298-89e4-3d4a40ef15b4", name: "Baju", - image: - "https://industripilar-api-staging.s3.amazonaws.com/media/uploads/categories/download.png", + image: null, }, { id: "0664247c-d9ea-4e56-bb02-4b8463f9e14c", @@ -172,8 +170,7 @@ test("Test tambah produk error", async () => { { id: "f0c08b4f-7421-4298-89e4-3d4a40ef15b4", name: "Baju", - image: - "https://industripilar-api-staging.s3.amazonaws.com/media/uploads/categories/download.png", + image: null, }, { id: "0664247c-d9ea-4e56-bb02-4b8463f9e14c", @@ -223,14 +220,28 @@ test("Test tambah produk error", async () => { <TambahProduk /> </AuthContext.Provider> ); + const name_produk = getByTestId("name-produk-input"); + await act(async () => { + await fireEvent.input(name_produk, { target: { value: "test" } }); + }); + const desc_produk = getByTestId("desc-produk-input"); + await act(async () => { + await fireEvent.input(desc_produk, { target: { value: "test" } }); + }); + const price_produk = getByTestId("price-produk-input"); + await act(async () => { + await fireEvent.input(price_produk, { target: { value: "1" } }); + }); const stock_produk = getByTestId("stock-produk-input"); await act(async () => { - await fireEvent.input(stock_produk, { target: { value: "-1" } }); + await fireEvent.input(stock_produk, { target: { value: "1" } }); }); await act(async () => { await fireEvent.submit(getByTestId("submit-produk")); }); const produk = getByTestId("tambah-produk"); - expect(produk.textContent).toContain("Error loading form !, Please relogin"); - expect(fetch.mock.calls.length).toEqual(3); + expect(produk.textContent).toContain( + "Error loading form !, Please relogin.." + ); + expect(fetch.mock.calls.length).toEqual(4); }); diff --git a/src/__test__/transaksi/DetailTransaksi.test.js b/src/__test__/transaksi/DetailTransaksi.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2a04ca61bcb165aba192b1a5945b1e75011a3a47 --- /dev/null +++ b/src/__test__/transaksi/DetailTransaksi.test.js @@ -0,0 +1,195 @@ +import { act, cleanup, fireEvent, render } from "@testing-library/react"; +import AuthContext from "../../utils/contex"; +import React from "react"; +import DetailTransaksi from "../../page/transaksi/DetailTransaksi"; +import { waitFor } from "@testing-library/dom"; + +beforeEach(() => { + fetch.resetMocks(); +}); +afterEach(cleanup); + +test("Test detail loaded TRANSFER", async () => { + fetch.mockResponseOnce( + JSON.stringify({ + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: "a", + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }) + ); + const { getByTestId } = render( + <AuthContext.Provider value={{ profile: { token: "tester" } }}> + <DetailTransaksi idTransaksi={"0c1db3b2-48f0-4604-83ca-e8f16e8550ab"} /> + </AuthContext.Provider> + ); + const wait = getByTestId("waiting-detail-transaksi"); + expect(wait.textContent).toContain("Fetching data.."); + await waitFor(() => getByTestId("page-detail-transaksi")); + const data = getByTestId("page-detail-transaksi"); + expect(data.textContent).toContain("Transfer"); + await act(async () => { + await fireEvent.click(getByTestId("button-see-proof")); + }); + const close = getByTestId("button-close-proof"); + expect(close.textContent).toContain("Close"); + await act(async () => { + await fireEvent.click(close); + }); + const dropdown = getByTestId("dropdown-status"); + expect(dropdown.children.length).toEqual(6); +}); + +test("Test detail loaded COD", async () => { + fetch.mockResponseOnce( + JSON.stringify({ + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "COD", + readable_payment_method: "Pembayaran di tempat", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: "a", + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }) + ); + const { getByTestId } = render( + <AuthContext.Provider value={{ profile: { token: "tester" } }}> + <DetailTransaksi idTransaksi={"0c1db3b2-48f0-4604-83ca-e8f16e8550ab"} /> + </AuthContext.Provider> + ); + const wait = getByTestId("waiting-detail-transaksi"); + expect(wait.textContent).toContain("Fetching data.."); + await waitFor(() => getByTestId("page-detail-transaksi")); + const data = getByTestId("page-detail-transaksi"); + expect(data.textContent).toContain("Cash On Delivery"); + const dropdown = getByTestId("dropdown-status"); + expect(dropdown.children.length).toEqual(5); +}); + +test("Test detail loaded error", async () => { + fetch.mockResponseOnce(JSON.stringify({}), { status: 400 }); + const { getByTestId } = render( + <AuthContext.Provider value={{ profile: { token: "tester" } }}> + <DetailTransaksi idTransaksi={"0c1db3b2-48f0-4604-83ca-e8f16e8550ab"} /> + </AuthContext.Provider> + ); + const wait = getByTestId("waiting-detail-transaksi"); + await waitFor(() => wait); + expect(wait.textContent).toContain("Something error"); +}); + +test("Test detail loaded submit", async () => { + fetch + .once( + JSON.stringify({ + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: "a", + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }) + ) + .once(JSON.stringify({}), { statusCode: 204 }); + const { getByTestId } = render( + <AuthContext.Provider value={{ profile: { token: "tester" } }}> + <DetailTransaksi idTransaksi={"0c1db3b2-48f0-4604-83ca-e8f16e8550ab"} /> + </AuthContext.Provider> + ); + const wait = getByTestId("waiting-detail-transaksi"); + expect(wait.textContent).toContain("Fetching data.."); + await waitFor(() => getByTestId("page-detail-transaksi")); + const data = getByTestId("page-detail-transaksi"); + expect(data.textContent).toContain("Transfer"); + const dropdown = getByTestId("dropdown-status"); + expect(dropdown.children.length).toEqual(6); + await act(async () => { + await fireEvent.click(getByTestId("button-submit-status")); + }); + expect(fetch.mock.calls.length).toEqual(2); +}); diff --git a/src/__test__/transaksi/ListTransaksi.test.js b/src/__test__/transaksi/ListTransaksi.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f9317b6e299407512fc5bbff8cbf7dfc33103ed1 --- /dev/null +++ b/src/__test__/transaksi/ListTransaksi.test.js @@ -0,0 +1,187 @@ +import { act, cleanup, fireEvent, render } from "@testing-library/react"; +import AuthContext from "../../utils/contex"; +import React from "react"; +import ListTransaksi from "../../page/transaksi/ListTransaksi"; +import { waitFor } from "@testing-library/dom"; + +beforeEach(() => { + fetch.resetMocks(); +}); +afterEach(cleanup); + +test(" Test List transaksi", async () => { + fetch.mockResponseOnce( + JSON.stringify({ + count: 17, + next: "https://industripilar-staging.herokuapp.com/transactions/?page=2", + previous: null, + results: [ + { + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: null, + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }, + ], + }) + ); + const { getByTestId } = render( + <AuthContext.Provider value={{ profile: { token: "tester" } }}> + <ListTransaksi /> + </AuthContext.Provider> + ); + await waitFor(() => getByTestId("tableList")); + const data = getByTestId("tableList"); + expect(data.textContent).toContain("whtestest"); + expect(data.textContent).toContain("Rp370.000"); + expect(data.textContent).toContain("18 April 2020 10:59"); + expect(fetch.mock.calls.length).toEqual(1); +}); + +test(" Test List transaksi filter", async () => { + fetch + .once( + JSON.stringify({ + count: 17, + next: + "https://industripilar-staging.herokuapp.com/transactions/?page=2", + previous: null, + results: [ + { + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "whtestest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: null, + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }, + ], + }) + ) + .once( + JSON.stringify({ + count: 17, + next: + "https://industripilar-staging.herokuapp.com/transactions/?page=2", + previous: null, + results: [ + { + id: "0c1db3b2-48f0-4604-83ca-e8f16e8550ab", + transaction_number: "H793P5ZK", + user: "d4b98bb5-8ba4-4a41-af10-93abcf53df58", + user_username: "filterwhtest", + user_full_name: "Michael Wiryadinata halim", + user_phone_number: "+628192090199", + shipping_address: "ada deh test", + shipping_neighborhood: "002", + shipping_hamlet: "002", + shipping_urban_village: "penggilingan", + shipping_sub_district: "Dummy Sub-District", + transaction_items: [ + { + id: "37f0298c-2e8a-4ab0-a094-e9177ddc60c4", + product: "3d403cd3-e356-4c15-9a86-8843333e2778", + product_code: "5VK6TY", + product_name: "produk barang", + product_price: "50000.00", + quantity: 7, + }, + ], + transaction_item_subtotal: "350000.00", + shipping_costs: "15000.00", + payment_method: "TRF", + readable_payment_method: "Transfer", + donation: "5000.00", + transaction_status: "002", + readable_transaction_status: "Waiting for seller confirmation", + proof_of_payment: null, + user_bank_account_name: "test", + user_bank_account_number: "1232131241321", + created_at: "2020-04-18T10:59:42.074386+07:00", + updated_at: "2020-04-18T11:00:18.150633+07:00", + subtotal: "370000.00", + }, + ], + }) + ); + const { getByTestId, getByLabelText } = render( + <AuthContext.Provider value={{ profile: { token: "tester" } }}> + <ListTransaksi /> + </AuthContext.Provider> + ); + await waitFor(() => getByTestId("tableList")); + const data = getByTestId("tableList"); + expect(data.textContent).toContain("whtestest"); + expect(data.textContent).toContain("Rp370.000"); + expect(data.textContent).toContain("18 April 2020 10:59"); + expect(fetch.mock.calls.length).toEqual(1); + await act(async () => { + await fireEvent.click(getByTestId("filter-button")); + }); + await act(async () => { + await fireEvent.input( + getByLabelText("Status transaksi:", { target: { value: "002" } }) + ); + await fireEvent.click(getByTestId("submit-filter")); + }); + expect(data.textContent).toContain("filterwhtest"); + expect(fetch.mock.calls.length).toEqual(2); +}); diff --git a/src/component/Sidebar.jsx b/src/component/Sidebar.jsx index c7e0159708cf20d51f4e425f18a7639254588f4b..4c9841d9bcbcdedb5405508ca5fc6b59e69f425f 100644 --- a/src/component/Sidebar.jsx +++ b/src/component/Sidebar.jsx @@ -50,7 +50,7 @@ const Sidebar = () => { ADMIN DASHBOARD </div> <LinkSidebar to="/produk">PRODUK</LinkSidebar> - <LinkSidebar to="/product1">TRANSAKSI</LinkSidebar> + <LinkSidebar to="/transaksi">TRANSAKSI</LinkSidebar> <LinkSidebar to="/program">PROGRAM</LinkSidebar> <LinkSidebar to="/product1">DONASI</LinkSidebar> <LinkSidebar to="/pengguna">PENGGUNA</LinkSidebar> diff --git a/src/component/TableComponent.jsx b/src/component/TableComponent.jsx index eacdd2ce778f304bc910358fdec6cc0edcf8510c..d8c11855c3bfc22fae559c7a08d337ffd3e4c09f 100644 --- a/src/component/TableComponent.jsx +++ b/src/component/TableComponent.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import useFetchList from "../utils/useFetchList"; import { useForm } from "react-hook-form"; import { css } from "@emotion/core"; @@ -11,11 +11,25 @@ import TableFooter from "@material-ui/core/TableFooter"; import TablePagination from "@material-ui/core/TablePagination"; import TableRow from "@material-ui/core/TableRow"; import Paper from "@material-ui/core/Paper"; -import { ButtonSearch, ErrorDiv, InputSearch } from "./html/html"; +import FilterListIcon from "@material-ui/icons/FilterList"; +import Collapse from "@material-ui/core/Collapse"; +import { + ButtonSearch, + ErrorDiv, + InputForm, + InputSearch, + InputSelectForm, + InputSubmitForm, + LabelInput, + RowInput, +} from "./html/html"; import { Search } from "@material-ui/icons"; import { Link } from "@reach/router"; import ArrowDropDownCircleIcon from "@material-ui/icons/ArrowDropDownCircle"; import TableHead from "@material-ui/core/TableHead"; +import Tooltip from "@material-ui/core/Tooltip"; +import IconButton from "@material-ui/core/IconButton"; +import Button from "@material-ui/core/Button"; const TableComponent = ({ url, @@ -24,7 +38,8 @@ const TableComponent = ({ title, keyValuePairs, link, - argument = "", + argument, + filter, }) => { const [ results, @@ -35,8 +50,15 @@ const TableComponent = ({ setPage, setStateSearch, setPageSize, - ] = useFetchList([url, pageDefault, searchDefault, 5, argument]); + setFilter, + ] = useFetchList([url, pageDefault, searchDefault, 5, argument, null]); const { register, handleSubmit } = useForm(); + const { + register: registerFilter, + handleSubmit: handleSubmitFilter, + reset: resetFilter, + } = useForm(); + const [filterTab, setFilterTab] = useState(false); return ( <div css={css` @@ -90,8 +112,110 @@ const TableComponent = ({ <Search /> </ButtonSearch> </form> + {filter !== null && filter !== undefined && ( + <Tooltip title="Filter list"> + <IconButton + data-testid="filter-button" + aria-label="filter list" + onClick={() => setFilterTab(!filterTab)} + > + <FilterListIcon /> + </IconButton> + </Tooltip> + )} </div> </Toolbar> + {filter !== null && filter !== undefined && ( + <Collapse in={filterTab} addEndListener={() => {}}> + <Toolbar> + <form + data-testid="filter-form" + onSubmit={handleSubmitFilter((data) => { + const filter = Object.entries(data) + .filter(([, val]) => val !== "") + .map(([key, val]) => `${key}=${val}`) + .join("&"); + setFilter(filter); + })} + css={css` + width: 100%; + display: flex; + flex-direction: column; + `} + > + {filter.map((field) => { + if (Array.isArray(field)) { + return ( + <RowInput key={field[0]}> + <LabelInput htmlFor={field[0]}> + {field[1]}: + </LabelInput> + <InputForm + id={field[0]} + type="date" + name={field[0]} + ref={registerFilter} + /> + </RowInput> + ); + } else if (typeof field === "object") { + const k = Object.keys(field); + return ( + <RowInput key={k[0]}> + <LabelInput htmlFor={k[0]}> + {field[k].label}: + </LabelInput> + <InputSelectForm + id={k[0]} + name={k[0]} + ref={registerFilter} + > + <option value="" /> + {field[k].choices.map((c) => { + const choice = Object.keys(c); + return ( + <option key={choice} value={choice}> + {c[choice]} + </option> + ); + })} + </InputSelectForm> + </RowInput> + ); + } + })} + <div + css={css` + display: flex; + width: 100%; + align-items: center; + align-content: space-between; + `} + > + <InputSubmitForm + css={css` + width: 50%; + margin-right: 2rem; + `} + data-testid="submit-filter" + type="submit" + value="Filter" + /> + <Button + variant="outlined" + color="secondary" + onClick={() => { + resetFilter(); + setFilter(null); + }} + > + Hapus Filter + </Button> + </div> + </form> + </Toolbar> + </Collapse> + )} <TableContainer> <Table> <TableHead> @@ -124,16 +248,11 @@ const TableComponent = ({ return ( <TableRow key={`row${indexR}-${u[keyValuePairs[0][0]]}`}> {keyValuePairs.slice(1).map((pairs, indexC) => { - if (pairs[0].toLowerCase() !== "dummy") { - return ( - <TableCell key={`${pairs[0]}r${indexR}c${indexC}`}> - {u[pairs[0]]} - </TableCell> - ); - } return ( <TableCell key={`${pairs[0]}r${indexR}c${indexC}`}> - Dummy + {pairs[2] !== undefined && pairs[2] !== null + ? pairs[2](u[pairs[0]]) + : u[pairs[0]]} </TableCell> ); })} @@ -152,7 +271,6 @@ const TableComponent = ({ <TableRow> <TablePagination rowsPerPageOptions={[5, 7, 10]} - colSpan={3} count={count || 0} rowsPerPage={stateSize} page={statePage - 1} diff --git a/src/component/TableUtils.jsx b/src/component/TableUtils.jsx new file mode 100644 index 0000000000000000000000000000000000000000..3ed474d61b52e0c7e07bc514e1f457a77acd7397 --- /dev/null +++ b/src/component/TableUtils.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import Moment from "react-moment"; +import NumberFormat from "react-number-format"; +import "moment-timezone"; +export const stringToDate = (date) => { + const dateFormat = new Date(date); + return ( + <Moment format={"DD MMMM YYYY HH:mm"} tz="Asia/Jakarta" date={dateFormat} /> + ); +}; + +export const stringToCurrency = (currency) => ( + <NumberFormat + value={currency} + isNumericString={true} + displayType={"text"} + thousandSeparator="." + decimalSeparator="," + decimalScale={0} + prefix={"Rp"} + /> +); diff --git a/src/component/status.jsx b/src/component/status.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ef8011f8ae0de65188e6b7066896b83c70217980 --- /dev/null +++ b/src/component/status.jsx @@ -0,0 +1,62 @@ +import React from "react"; +import { css } from "@emotion/core"; +const Status = ({ status, label }) => { + switch (status) { + case "001": + return ( + <div + css={css` + color: darkgoldenrod; + `} + > + {label} + </div> + ); + case "002": + return ( + <div + css={css` + color: red; + `} + > + {label} + </div> + ); + case "003": + return ( + <div + css={css` + color: #004444; + `} + > + {label} + </div> + ); + case "004": + return ( + <div + css={css` + color: #660066; + `} + > + {label} + </div> + ); + case "005": + return <div>{label}</div>; + case "006": + return <div>{label}</div>; + case "007": + return ( + <div + css={css` + color: darkred; + `} + > + {label} + </div> + ); + } +}; + +export default Status; diff --git a/src/page/pengguna/DetailPengguna.jsx b/src/page/pengguna/DetailPengguna.jsx index 79db0a57f0342a732113269b4b1257372bc4f2cb..69445b4d17fcfe59daaa7977714f30fc6cc4cb7e 100644 --- a/src/page/pengguna/DetailPengguna.jsx +++ b/src/page/pengguna/DetailPengguna.jsx @@ -1,20 +1,54 @@ import React from "react"; import { css } from "@emotion/core"; import { ArrowBack } from "@material-ui/icons"; -import { Link } from "@reach/router"; +import { navigate } from "@reach/router"; import PersonIcon from "@material-ui/icons/Person"; import PhoneIcon from "@material-ui/icons/Phone"; import LocationOnIcon from "@material-ui/icons/LocationOn"; import ScheduleIcon from "@material-ui/icons/Schedule"; import useFetchSingleData from "../../utils/useFetchSingleData"; import { ErrorDiv } from "../../component/html/html"; +import { stringToCurrency, stringToDate } from "../../component/TableUtils"; +import TableComponent from "../../component/TableComponent"; const DetailPengguna = ({ userId }) => { const url = `${process.env.REACT_APP_BASE_URL}/users/${userId}/`; const [user, error] = useFetchSingleData(url); + const data = { + url: `${process.env.REACT_APP_BASE_URL}/transactions/`, + pageDefault: 1, + argument: `user=${userId}`, + title: "Riwayat Transaksi", + keyValuePairs: [ + ["id", "id"], + ["transaction_number", "ID Transaksi"], + ["created_at", "Tanggal Pembuatan", stringToDate], + ["updated_at", "Tanggal Update", stringToDate], + ["readable_transaction_status", "Status"], + ["subtotal", "Total", stringToCurrency], + ], + link: "/transaksi/", + filter: [ + ["updated_at_before", "Updated At Before"], + ["updated_at_after", "Updated At After"], + { + transaction_status: { + label: "Status transaksi", + choices: [ + { "001": "Waiting for proof of payment" }, + { "002": "Waiting for seller confirmation" }, + { "003": "In process" }, + { "004": "Being shipped" }, + { "005": "Completed" }, + { "006": "Canceled" }, + ], + }, + }, + ], + }; return ( <div - data-testid="page" + data-testid="page-profile" css={css` width: 100%; height: 100%; @@ -36,9 +70,19 @@ const DetailPengguna = ({ userId }) => { line-height: 3.4rem; `} > - <Link to="/pengguna" style={{ color: "#000000" }}> + <button + css={css` + background-color: Transparent; + background-repeat: no-repeat; + border: none; + cursor: pointer; + overflow: hidden; + outline: none; + `} + onClick={() => navigate(-1)} + > <ArrowBack fontSize="large" /> - </Link> + </button> <div css={css` display: inline; @@ -149,16 +193,10 @@ const DetailPengguna = ({ userId }) => { <div css={css` display: flex; + flex-direction: column; `} > - <ScheduleIcon style={{ fontSize: 25, color: "FFC80A" }} /> - <div - css={css` - margin-left: 2rem; - `} - > - Riwayat Transaksi - </div> + <TableComponent {...data} /> </div> </div> <div diff --git a/src/page/produk/DetailProduk.jsx b/src/page/produk/DetailProduk.jsx index 3ea286a80fc34ac018a8909799f9560cd56e3d46..94890a51a2a10dba8b947566cbb53a6e0c25d1d3 100644 --- a/src/page/produk/DetailProduk.jsx +++ b/src/page/produk/DetailProduk.jsx @@ -11,7 +11,7 @@ import { AttachMoney, LocalShipping, } from "@material-ui/icons"; -import { Link } from "@reach/router"; +import { Link, navigate } from "@reach/router"; import useFetchSingleData from "../../utils/useFetchSingleData"; import useDelete from "../../utils/useDelete"; import { @@ -22,6 +22,7 @@ import { DialogTitle, Button, } from "@material-ui/core"; +import { stringToCurrency } from "../../component/TableUtils"; const DetailProduk = ({ productId }) => { const url = `${process.env.REACT_APP_BASE_URL}/products/${productId}/`; @@ -81,9 +82,19 @@ const DetailProduk = ({ productId }) => { line-height: 3.4rem; `} > - <Link to="/produk" style={{ color: "#000000" }}> + <button + css={css` + background-color: Transparent; + background-repeat: no-repeat; + border: none; + cursor: pointer; + overflow: hidden; + outline: none; + `} + onClick={() => navigate(-1)} + > <ArrowBack fontSize="large" /> - </Link> + </button> <div css={css` display: inline; @@ -218,7 +229,7 @@ const DetailProduk = ({ productId }) => { margin-left: 1%; `} > - Rp {product.price} + {stringToCurrency(product.price)} </span> </span> </p> diff --git a/src/page/produk/ListProduk.jsx b/src/page/produk/ListProduk.jsx index 8f4a5ee4dd5a21af1397ab8ec70b6a25addec217..021f6bd253f8e45f178062ce8e82494f9e12d30c 100644 --- a/src/page/produk/ListProduk.jsx +++ b/src/page/produk/ListProduk.jsx @@ -2,6 +2,7 @@ import React from "react"; import TableComponent from "../../component/TableComponent"; import { css } from "@emotion/core"; import LinkYellow from "../../component/LinkYellow"; +import { stringToCurrency } from "../../component/TableUtils"; const ListProduk = () => { const data = { @@ -12,7 +13,7 @@ const ListProduk = () => { keyValuePairs: [ ["id", "id"], ["name", "Nama Produk"], - ["price", "Harga(Rp)"], + ["price", "Harga", stringToCurrency], ["stock", "Stok"], ], link: "", diff --git a/src/page/transaksi/DetailTransaksi.jsx b/src/page/transaksi/DetailTransaksi.jsx new file mode 100644 index 0000000000000000000000000000000000000000..93df7989bfa6d17bc7a2a29ab6edaff5e5b7d03f --- /dev/null +++ b/src/page/transaksi/DetailTransaksi.jsx @@ -0,0 +1,379 @@ +import React, { useState } from "react"; +import useFetchSingleData from "../../utils/useFetchSingleData"; +import { css } from "@emotion/core"; +import { ArrowBack, Contacts, Phone, Photo } from "@material-ui/icons"; +import { Link, navigate } from "@reach/router"; +import PersonIcon from "@material-ui/icons/Person"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Paper from "@material-ui/core/Paper"; +import Status from "../../component/status"; +import { stringToCurrency, stringToDate } from "../../component/TableUtils"; +import { ErrorDiv } from "../../component/html/html"; +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import FormStatus from "./FormStatus"; +const DetailTransaksi = ({ idTransaksi }) => { + const url = `${process.env.REACT_APP_BASE_URL}/transactions/${idTransaksi}`; + const [transaction, error] = useFetchSingleData(url); + const [dialogOpen, setDialogOpen] = useState(false); + + const handleClickOpen = () => { + setDialogOpen(true); + }; + + const handleClose = () => { + setDialogOpen(false); + }; + + if (Object.keys(transaction).length === 0) + return ( + <div + data-testid="waiting-detail-transaksi" + css={css` + display: flex; + margin: 2rem 3rem 3rem 3rem; + flex-direction: column; + font-size: 25px; + `} + > + {error && <ErrorDiv>Something error</ErrorDiv>} + Fetching data.. + </div> + ); + return ( + <div + data-testid="page-detail-transaksi" + css={css` + display: flex; + margin: 2rem 3rem 3rem 3rem; + flex-direction: column; + justify-content: space-around; + height: 125vh; + `} + > + {error && <ErrorDiv>Something error</ErrorDiv>} + <button + css={css` + align-self: start; + background-color: Transparent; + background-repeat: no-repeat; + border: none; + cursor: pointer; + overflow: hidden; + outline: none; + `} + onClick={() => navigate(-1)} + > + <ArrowBack fontSize="large" /> + </button> + <div + css={css` + font-size: 1rem; + `} + > + ADMIN - KELOLA TRANSAKSI + </div> + <div + css={css` + font-size: 1.5rem; + display: flex; + align-content: space-between; + `} + > + <div + css={css` + display: flex; + `} + > + <div + css={css` + margin-right: 1rem; + `} + > + Transaction code: + </div> + <div>{transaction.transaction_number}</div> + </div> + <FormStatus + {...{ + idTransaksi, + defaultStatus: transaction.transaction_status, + paymentMethod: transaction.payment_method, + }} + /> + </div> + <div + css={css` + font-size: 1.5rem; + display: flex; + `} + > + <div + css={css` + margin-right: 1rem; + `} + > + Status:{" "} + </div> + <Status + label={transaction.readable_transaction_status} + status={transaction.transaction_status} + /> + </div> + <div + css={css` + font-size: 1.2rem; + display: flex; + `} + > + <div + css={css` + margin-right: 0.5rem; + `} + > + Date created:{" "} + </div> + <div>{stringToDate(transaction.created_at)}</div> + </div> + <div + css={css` + font-size: 1.2rem; + display: flex; + `} + > + <div + css={css` + margin-right: 0.5rem; + `} + > + Date updated:{" "} + </div> + <div>{stringToDate(transaction.updated_at)}</div> + </div> + <div + css={css` + display: flex; + font-size: 1.2rem; + align-items: baseline; + `} + > + <PersonIcon /> + <Link to={`/pengguna/${transaction.user}`}> + {transaction.user_full_name} / {transaction.user_username} + </Link> + </div> + + <div + css={css` + display: flex; + font-size: 1.2rem; + align-items: baseline; + `} + > + <Phone /> + <div>{transaction.user_phone_number}</div> + </div> + <div + css={css` + display: flex; + flex-direction: column; + `} + > + <div + css={css` + font-size: 1.2rem; + `} + > + <Contacts /> + Alamat Pengiriman + <div>{`${transaction.shipping_address}, + RT ${transaction.shipping_neighborhood}, + RW ${transaction.shipping_hamlet}, + Kelurahan ${transaction.shipping_urban_village}, + Kecamatan ${transaction.shipping_sub_district}`}</div> + </div> + </div> + <TableContainer component={Paper}> + <Table aria-label="simple table"> + <TableHead> + <TableRow> + <TableCell>Product Code</TableCell> + <TableCell>Product Name</TableCell> + <TableCell>Quantity</TableCell> + <TableCell>Product Price</TableCell> + <TableCell>Subtotal</TableCell> + </TableRow> + </TableHead> + <TableBody> + {transaction.transaction_items.map((row) => ( + <TableRow key={row.id}> + <TableCell component="th" scope="row"> + <Link to={`/produk/${row.product}`}>{row.product_code}</Link> + </TableCell> + <TableCell>{row.product_name}</TableCell> + <TableCell>{row.quantity}</TableCell> + <TableCell>{stringToCurrency(row.product_price)}</TableCell> + <TableCell> + {stringToCurrency( + `${Number.parseFloat(row.product_price) * row.quantity}` + )} + </TableCell> + </TableRow> + ))} + <TableRow> + <TableCell rowSpan={3} colSpan={3} /> + <TableCell align="left" colSpan={1}> + Donasi + </TableCell> + <TableCell align="left"> + {stringToCurrency(transaction.donation)} + </TableCell> + </TableRow> + <TableRow> + <TableCell align="left" colSpan={1}> + Shipping cost + </TableCell> + <TableCell align="left"> + {stringToCurrency(transaction.shipping_costs)} + </TableCell> + </TableRow> + <TableRow> + <TableCell colSpan={1} align="left"> + Total + </TableCell> + <TableCell align="left"> + {stringToCurrency(transaction.subtotal)} + </TableCell> + </TableRow> + </TableBody> + </Table> + </TableContainer> + <div + css={css` + display: flex; + flex-direction: column; + `} + > + {" "} + <div + css={css` + font-size: 1.5rem; + `} + > + Pembayaran + </div> + {transaction.payment_method === "COD" && ( + <div + css={css` + font-size: 1.3rem; + `} + > + Cash On Delivery + </div> + )} + {transaction.payment_method === "TRF" && ( + <div + css={css` + font-size: 1.3rem; + `} + > + Transfer + <div> + {transaction.proof_of_payment === null ? ( + <div>User belum mengirimkan bukti pembayaran</div> + ) : ( + <div + css={css` + display: flex; + flex-direction: column; + `} + > + <div + css={css` + display: flex; + font-size: 1.2rem; + align-items: baseline; + `} + > + <div + css={css` + margin-right: 1rem; + `} + > + Sender name:{" "} + </div> + <div>{transaction.user_bank_account_name}</div> + </div> + <div + css={css` + display: flex; + font-size: 1.2rem; + align-items: baseline; + `} + > + <div + css={css` + margin-right: 1rem; + `} + > + Sender account number:{" "} + </div> + <div>{transaction.user_bank_account_number}</div> + </div> + <Button + onClick={handleClickOpen} + variant="contained" + color="primary" + size="medium" + startIcon={<Photo />} + data-testid="button-see-proof" + > + Bukti + </Button> + </div> + )} + </div> + </div> + )} + </div> + <Dialog + maxWidth="xl" + open={dialogOpen} + onClose={handleClose} + aria-labelledby="max-width-dialog-title" + > + <DialogTitle id="max-width-dialog-title">Bukti Pembayaran</DialogTitle> + <DialogContent> + <img + css={css` + height: 80vh; + width: 80vw; + object-fit: contain; + `} + src={transaction.proof_of_payment} + alt="Bukti Pembayaran" + /> + </DialogContent> + <DialogActions> + <Button + data-testid="button-close-proof" + onClick={handleClose} + color="primary" + > + Close + </Button> + </DialogActions> + </Dialog> + </div> + ); +}; + +export default DetailTransaksi; diff --git a/src/page/transaksi/FormStatus.jsx b/src/page/transaksi/FormStatus.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e97a90fae126771ab1365aa230e19c86d791fa7d --- /dev/null +++ b/src/page/transaksi/FormStatus.jsx @@ -0,0 +1,77 @@ +import React from "react"; +import { useForm } from "react-hook-form"; +import useSendData from "../../utils/useSendData"; +import { + ErrorDiv, + InputSelectForm, + LabelInput, + RowInput, +} from "../../component/html/html"; +import { css } from "@emotion/core"; +import Button from "@material-ui/core/Button"; +import { Save } from "@material-ui/icons"; + +const FormStatus = ({ idTransaksi, defaultStatus, paymentMethod }) => { + const url = `${process.env.REACT_APP_BASE_URL}/transactions/${idTransaksi}`; + const { register, handleSubmit } = useForm({ + defaultValues: { + transaction_status: defaultStatus, + }, + }); + const [send, errorSend] = useSendData({ + url, + header: { "Content-Type": "application/json" }, + method: "PUT", + redirect: `/transaksi/`, + }); + + const onSubmit = (data) => { + send(JSON.stringify({ ...data })); + }; + return ( + <div data-testid="form-status"> + {errorSend && <ErrorDiv>Status transaksi tidak dapat disimpan</ErrorDiv>} + <form + onSubmit={handleSubmit(onSubmit)} + css={css` + display: flex; + `} + > + <RowInput> + <LabelInput + htmlFor="status" + css={css` + margin-right: 1rem; + `} + /> + <InputSelectForm + data-testid="dropdown-status" + id="status" + ref={register({ required: true })} + name="transaction_status" + > + {paymentMethod === "TRF" && ( + <option value="001">Waiting for proof of payment</option> + )} + <option value="002">Waiting for seller confirmation</option> + <option value="003">In process</option> + <option value="004">Being shipped</option> + <option value="005">Completed</option> + <option value="006">Canceled</option> + </InputSelectForm> + </RowInput> + <Button + data-testid="button-submit-status" + type="submit" + variant="contained" + color="primary" + startIcon={<Save />} + > + Simpan + </Button> + </form> + </div> + ); +}; + +export default FormStatus; diff --git a/src/page/transaksi/ListTransaksi.jsx b/src/page/transaksi/ListTransaksi.jsx new file mode 100644 index 0000000000000000000000000000000000000000..129aa6ce0506e60f746824199a8f299b5853c141 --- /dev/null +++ b/src/page/transaksi/ListTransaksi.jsx @@ -0,0 +1,82 @@ +import React from "react"; +import TableComponent from "../../component/TableComponent"; +import { css } from "@emotion/core"; +import LinkYellow from "../../component/LinkYellow"; +import { stringToCurrency, stringToDate } from "../../component/TableUtils"; + +const ListTransaksi = () => { + const data = { + url: `${process.env.REACT_APP_BASE_URL}/transactions/`, + pageDefault: 1, + title: "", + keyValuePairs: [ + ["id", "id"], + ["transaction_number", "ID Transaksi"], + ["user_username", "Username"], + ["created_at", "Tanggal Pembuatan", stringToDate], + ["updated_at", "Tanggal Update", stringToDate], + ["readable_transaction_status", "Status"], + ["subtotal", "Total", stringToCurrency], + ], + link: "/transaksi/", + filter: [ + ["updated_at_date_range_before", "Updated At Before"], + ["updated_at_date_range_after", "Updated At After"], + { + transaction_status: { + label: "Status transaksi", + choices: [ + { "001": "Waiting for proof of payment" }, + { "002": "Waiting for seller confirmation" }, + { "003": "In process" }, + { "004": "Being shipped" }, + { "005": "Completed" }, + { "006": "Canceled" }, + ], + }, + }, + ], + }; + return ( + <div + css={css` + display: flex; + flex-direction: column; + margin: 2rem 3rem 3rem 3rem; + `} + > + <div + css={css` + font-size: 35px; + `} + > + KELOLA TRANSAKSI + </div> + <div + css={css` + width: 100%; + justify-content: space-between; + display: flex; + flex-direction: row; + margin-bottom: 1.8rem; + margin-top: 1rem; + `} + > + <div + css={css` + display: flex; + width: 35%; + `} + > + <LinkYellow to="/subkategori">SUBKATEGORI</LinkYellow> + <LinkYellow className="ml-2" to="/kategori"> + KATEGORI + </LinkYellow> + </div> + </div> + <TableComponent {...data} /> + </div> + ); +}; + +export default ListTransaksi; diff --git a/src/routes.jsx b/src/routes.jsx index f6158901317373160146fb0657c591d87ccc2e25..10f043d398785a506e0acaff47a6177a9ccb35de 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -23,6 +23,9 @@ import DetailProgram from "./page/program/DetailProgram"; import TambahProgram from "./page/program/TambahProgram"; import EditProgram from "./page/program/EditProgram"; +import ListTransaksi from "./page/transaksi/ListTransaksi"; +import DetailTransaksi from "./page/transaksi/DetailTransaksi"; + const Placeholder = ({ children }) => children; const Routes = () => { @@ -64,6 +67,11 @@ const Routes = () => { <ProtectedRoute path=":idProgram/ubah" component={EditProgram} /> </Placeholder> + <Placeholder path="transaksi"> + <ProtectedRoute path="/" component={ListTransaksi} /> + <ProtectedRoute path=":idTransaksi" component={DetailTransaksi} /> + </Placeholder> + <UnauthenticatedRoute path="/" component={Login} /> </Router> ); diff --git a/src/utils/useFetchList.jsx b/src/utils/useFetchList.jsx index a17e8fc7a3d3600f496f4d752df495efb96cf1cb..8cc283050e1044f96f85a4ffe2167836c6f49d3a 100644 --- a/src/utils/useFetchList.jsx +++ b/src/utils/useFetchList.jsx @@ -6,7 +6,8 @@ const useFetchList = ([ defaultPage, defaultSearch, defaultSize, - argument = "", + argument, + filterString, ]) => { const [statePage, setPage] = useState(defaultPage); const [stateSearch, setStateSearch] = useState(defaultSearch); @@ -14,15 +15,30 @@ const useFetchList = ([ const [results, setResults] = useState([]); const [errorState, setErrorState] = useState(false); const [count, setCount] = useState(0); + const [filter, setFilter] = useState(filterString); const { profile } = useAuthContext(); useEffect(() => { const controller = new AbortController(); setErrorState(false); let url = urlString.concat("?"); - url = statePage !== null ? url.concat(`page=${statePage}&`) : url; - url = stateSize !== null ? url.concat(`page_size=${stateSize}&`) : url; - url = argument !== null ? url.concat(`${argument}&`) : url; - url = stateSearch !== null ? url.concat(`search=${stateSearch}`) : url; + url = + statePage !== null && statePage !== undefined + ? url.concat(`page=${statePage}&`) + : url; + url = + stateSize !== null && stateSize !== undefined + ? url.concat(`page_size=${stateSize}&`) + : url; + url = + argument !== null && argument !== undefined + ? url.concat(`${argument}&`) + : url; + url = + filter !== null && filter !== undefined ? url.concat(`${filter}&`) : url; + url = + stateSearch !== null && stateSearch !== undefined + ? url.concat(`search=${stateSearch}`) + : url; trackPromise( fetch(url, { method: "GET", @@ -45,7 +61,15 @@ const useFetchList = ([ .catch((e) => setErrorState(!(e instanceof DOMException))) ); return () => controller.abort(); - }, [statePage, stateSearch, stateSize, argument, urlString, profile.token]); + }, [ + statePage, + stateSearch, + stateSize, + argument, + urlString, + profile.token, + filter, + ]); return [ results, errorState, @@ -55,6 +79,7 @@ const useFetchList = ([ setPage, setStateSearch, setPageSize, + setFilter, ]; }; export default useFetchList;