From e04d91587b35102f226975bbf46d074f3982fe21 Mon Sep 17 00:00:00 2001 From: Damillora Date: Mon, 29 Mar 2021 14:08:26 +0700 Subject: [PATCH] For quiz --- .vscode/launch.json | 14 ++- Next/APIClient.ts | 6 +- Next/components/Errors.tsx | 6 ++ Next/components/ProductForm.tsx | 47 +++++++++ Next/interfaces/product.ts | 23 +++++ Next/pages/customer/edit/[id].tsx | 141 +++++++++++++++++++++++++++ Next/pages/customer/index.tsx | 2 + Next/pages/product/create.tsx | 116 +++++++++++++++++++++++ Next/pages/product/edit/[id].tsx | 152 ++++++++++++++++++++++++++++++ Next/pages/product/index.tsx | 130 +++++++++++++++++++++++++ TestAppRuna/Shop.sqlite.db | Bin 36864 -> 36864 bytes 11 files changed, 630 insertions(+), 7 deletions(-) create mode 100644 Next/components/Errors.tsx create mode 100644 Next/components/ProductForm.tsx create mode 100644 Next/interfaces/product.ts create mode 100644 Next/pages/customer/edit/[id].tsx create mode 100644 Next/pages/product/create.tsx create mode 100644 Next/pages/product/edit/[id].tsx create mode 100644 Next/pages/product/index.tsx diff --git a/.vscode/launch.json b/.vscode/launch.json index 799386b..d3d2d05 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,13 +2,19 @@ "version": "0.2.0", "configurations": [ { - "command": "yarn dev", - "name": "Next.js", + "type": "node", "request": "launch", + "name": "Next", + "runtimeExecutable": "yarn", "cwd": "${workspaceFolder}/Next", - "type": "node-terminal" + "runtimeArgs": [ + "dev" + ], + "port": 9229, + "skipFiles": [ + "/**" + ] }, - { "name": "TestAppRuna", "type": "coreclr", diff --git a/Next/APIClient.ts b/Next/APIClient.ts index 3941fa7..bb3af2f 100644 --- a/Next/APIClient.ts +++ b/Next/APIClient.ts @@ -664,8 +664,8 @@ export interface CartDeleteModel { export interface CustomerListItem { customerID: string; - name?: string | undefined; - email?: string | undefined; + name: string; + email: string; } export interface CustomerCreateUpdateModel { @@ -675,7 +675,7 @@ export interface CustomerCreateUpdateModel { export interface ProductListItem { productID: string; - name?: string | undefined; + name: string; price: number; } diff --git a/Next/components/Errors.tsx b/Next/components/Errors.tsx new file mode 100644 index 0000000..b2e14f4 --- /dev/null +++ b/Next/components/Errors.tsx @@ -0,0 +1,6 @@ + +export default function Errors({ errors }) { + return errors.map(x => ( +
  • {x}
  • + )); +} \ No newline at end of file diff --git a/Next/components/ProductForm.tsx b/Next/components/ProductForm.tsx new file mode 100644 index 0000000..767f8e7 --- /dev/null +++ b/Next/components/ProductForm.tsx @@ -0,0 +1,47 @@ +import { faChevronUp } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React, { useEffect, useState } from "react"; + + +export default function ProductForm({ name, price, onNameChange, onPriceChange, onSubmit, busy }) { + let [form,setForm] = useState({ + name: "", + value: "", + }) + let [formBusy,setFormBusy] = useState(false); + let nameChanged = (e: React.ChangeEvent) => { + name = e.target.value; + onNameChange(e.target.value); + } + let priceChanged = (e: React.ChangeEvent) => { + price = e.target.value; + onPriceChange(e.target.value); + } + let submit = (e: React.MouseEvent) => { + e.preventDefault() + onSubmit(); + } + formBusy = busy; + useEffect(() => { + setFormBusy(busy); + },[busy]); + return ( +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + ); +} \ No newline at end of file diff --git a/Next/interfaces/product.ts b/Next/interfaces/product.ts new file mode 100644 index 0000000..2c1cdd6 --- /dev/null +++ b/Next/interfaces/product.ts @@ -0,0 +1,23 @@ +import { ProductListItem } from "../APIClient"; + +export interface ProductDataProps { + productID: string, +} +export interface DeleteProductProps extends ProductDataProps { + onDelete: (val) => void +} +export interface ProductListRowsProps { + products: ProductListItem[], + onDelete: (val) => void, +} +export interface ProductState { + product: ProductListItem[] +} +export interface ProductFormState { + form: { + name: string, + price: number, + }, + errors: string[], + busy: boolean, +} \ No newline at end of file diff --git a/Next/pages/customer/edit/[id].tsx b/Next/pages/customer/edit/[id].tsx new file mode 100644 index 0000000..5f8ec06 --- /dev/null +++ b/Next/pages/customer/edit/[id].tsx @@ -0,0 +1,141 @@ +import React, { ChangeEvent } from 'react'; +import { withRouter, NextRouter } from 'next/router'; +import { Layout } from '../../shared/Layout'; +import { CustomerClient } from '../../../APIClient'; +import Swal from 'sweetalert2'; + +function Errors({ errors }) { + return errors.map(x => ( +
  • {x}
  • + )); +} +class EditCustomer extends React.Component<{ + router: NextRouter, +}, { + form: { + name: string, + email: string, + }, + errors: string[], + busy: boolean, +}> { + constructor(props) { + super(props) + const router = props.router; + const { id } = router.query!; + this.state = { + form: { + name: "", + email: "", + }, + errors: [], + busy: false, + } + } + async componentDidMount() { + const client = new CustomerClient(); + console.log(this.props.router) + if (this.props.router.query['id']) { + let data = await client.get(this.props.router.query['id']); + this.setState({ + form: { + name: data.name, + email: data.email, + } + }); + } + } + onNameChanged(e: ChangeEvent) { + this.setState({ + form: { + name: e.target.value, + email: this.state.form.email, + } + }); + } + onEmailChanged(e: ChangeEvent) { + this.setState({ + form: { + name: this.state.form.name, + email: e.target.value + } + }); + } + async validate() { + let errors: string[] = []; + if (!this.state.form.name) { + errors.push("Name required"); + } + if (!this.state.form.email) { + errors.push("Email required"); + } + await new Promise((resolve) => { + this.setState({ + errors + }, () => resolve(undefined)); + }) + return errors.length == 0; + } + async onSubmit(e) { + e.preventDefault(); + if (await this.validate()) { + await new Promise((resolve) => { + this.setState({ + busy: true, + }, () => resolve(undefined)); + }) + try { + const form = this.state.form; + const client = new CustomerClient(); + await client.put(this.props.router.query.id, { + name: form.name, + email: form.email, + }) + Swal.fire({ + title: "Submitted!", + text: "Customer is now in database", + }) + } catch (error) { + + } finally { + await new Promise((resolve) => { + this.setState({ + busy: false, + }, () => resolve(undefined)); + }) + + } + } + } + render() { + return ( +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
  • + +
  • +
    +
    + ) + } +} + +export default function EditCustomerPage() { + let EditCustomerMain = withRouter(EditCustomer); + return ( + + + + ) +} \ No newline at end of file diff --git a/Next/pages/customer/index.tsx b/Next/pages/customer/index.tsx index 1bf904d..dfeec60 100644 --- a/Next/pages/customer/index.tsx +++ b/Next/pages/customer/index.tsx @@ -35,6 +35,7 @@ const CustomerListItemRows: React.FunctionComponent<{ let rows = customers.map(x => { return ( + {x.customerID} {x.name} {x.email} @@ -87,6 +88,7 @@ class Customer extends React.Component<{}, { customer: CustomerListItem[] }> { return ( + diff --git a/Next/pages/product/create.tsx b/Next/pages/product/create.tsx new file mode 100644 index 0000000..df586cd --- /dev/null +++ b/Next/pages/product/create.tsx @@ -0,0 +1,116 @@ +import React, { ChangeEvent } from 'react'; +import { Layout } from '../shared/Layout'; +import { ProductClient } from '../../APIClient'; +import Swal from 'sweetalert2'; +import ProductForm from '../../components/ProductForm'; +import Errors from '../../components/Errors'; +import Link from 'next/link'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; +import { ProductFormState } from '../../interfaces/product'; + +class CreateProduct extends React.Component<{}, ProductFormState> { + constructor(props) { + super(props) + this.state = { + form: { + name: "", + price: 0, + }, + errors: [], + busy: false, + } + } + onNameChanged(newName) { + this.setState({ + form: { + name: newName, + price: this.state.form.price, + } + }); + } + onPriceChanged(newPrice) { + this.setState({ + form: { + name: this.state.form.name, + price: newPrice + } + }); + } + async validate() { + let errors: string[] = []; + if (!this.state.form.name) { + errors.push("Name required"); + } + if (!this.state.form.price) { + errors.push("Price required"); + } + await new Promise((resolve) => { + this.setState({ + errors + }, () => resolve(undefined)); + }) + return errors.length == 0; + } + async onSubmit() { + if (await this.validate()) { + await new Promise((resolve) => { + this.setState({ + busy: true, + }, () => resolve(undefined)); + }) + try { + const form = this.state.form; + const client = new ProductClient(); + await client.post({ + name: form.name, + price: form.price, + }) + Swal.fire({ + title: "Submitted!", + text: "Product is now in database", + }) + } catch (error) { + + } finally { + await new Promise((resolve) => { + this.setState({ + busy: false, + }, () => resolve(undefined)); + }) + + } + } + } + render() { + return ( +
    +

    Create Product

    + + + Return to index + + + +
      + +
    +
    + ) + } +} + + +export default function CreateProductPage() { + return ( + + + + ) +} \ No newline at end of file diff --git a/Next/pages/product/edit/[id].tsx b/Next/pages/product/edit/[id].tsx new file mode 100644 index 0000000..6dac913 --- /dev/null +++ b/Next/pages/product/edit/[id].tsx @@ -0,0 +1,152 @@ +import React, { ChangeEvent } from 'react'; +import { withRouter, NextRouter, useRouter } from 'next/router'; +import { Layout } from '../../shared/Layout'; +import { ProductClient } from '../../../APIClient'; +import Swal from 'sweetalert2'; +import ProductForm from '../../../components/ProductForm'; +import Link from 'next/link'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; +import { ProductDataProps, ProductFormState } from '../../../interfaces/product'; + +function Errors({ errors }) { + return errors.map(x => ( +
  • {x}
  • + )); +} +class EditProduct extends React.Component { + constructor(props) { + super(props) + const id = props.productID; + this.state = { + form: { + name: "", + price: 0, + }, + errors: [], + busy: false, + } + } + async componentDidMount() { + const client = new ProductClient(); + await new Promise((resolve) => { + this.setState({ + busy: true, + }, () => resolve(undefined)); + }) + if (this.props.productID) { + let data = await client.get(this.props.productID); + await new Promise((resolve) => { + this.setState({ + busy: false, + }, () => resolve(undefined)); + }) + this.setState({ + form: { + name: data.name, + price: data.price, + } + }); + } + } + onNameChanged(newName: string) { + this.setState({ + form: { + name: newName, + price: this.state.form.price, + } + }); + } + onPriceChanged(newPrice: number) { + this.setState({ + form: { + name: this.state.form.name, + price: newPrice, + } + }); + } + async validate() { + let errors: string[] = []; + if (!this.state.form.name) { + errors.push("Name required"); + } + if (!this.state.form.price) { + errors.push("Email required"); + } + await new Promise((resolve) => { + this.setState({ + errors + }, () => resolve(undefined)); + }) + return errors.length == 0; + } + async onSubmit() { + if (await this.validate()) { + await new Promise((resolve) => { + this.setState({ + busy: true, + }, () => resolve(undefined)); + }) + try { + const form = this.state.form; + const client = new ProductClient(); + await client.put(this.props.productID, { + name: form.name, + price: form.price, + }) + Swal.fire({ + title: "Submitted!", + text: "Product is modified in database", + }) + } catch (error) { + + } finally { + await new Promise((resolve) => { + this.setState({ + busy: false, + }, () => resolve(undefined)); + }) + + } + } + } + render() { + if (typeof this.props.productID != "string") { + return ( +
    +
    + ) + } + return ( +
    +

    Edit Product

    + + + Return to index + + + +
      + +
    +
    + ) + } +} + +export default function EditProductPage() { + const router = useRouter(); + const id = router.query['id']; + + return ( + + + + ) +} \ No newline at end of file diff --git a/Next/pages/product/index.tsx b/Next/pages/product/index.tsx new file mode 100644 index 0000000..1cbbdaa --- /dev/null +++ b/Next/pages/product/index.tsx @@ -0,0 +1,130 @@ +import React from 'react'; +import axios from 'axios'; +import { ProductClient, ProductListItem } from '../../APIClient'; +import { Layout } from '../shared/Layout'; +import Swal from 'sweetalert2'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faEdit, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { useRouter } from 'next/router'; +import Link from 'next/link'; +import { DeleteProductProps, ProductDataProps, ProductListRowsProps, ProductState } from '../../interfaces/product'; + +const DeleteProductButton: React.FunctionComponent = ({ productID, onDelete }) => { + const onClickDelete = async () => { + const result = await Swal.fire({ + title: 'Confirm delete?', + text: 'Delete product?', + icon: 'warning', + showCancelButton: true, + }) + if (result.isConfirmed) { + onDelete(productID); + } + } + return ( + + ); +} +const EditProductButton: React.FunctionComponent = ({ productID }) => { + const router = useRouter(); + return ( + + + + + + ); +} +const ProductListItemRows: React.FunctionComponent = ({ products, onDelete }) => { + let rows = products.map(x => { + return ( +
    + + + + + + + ); + }) + return ( + + {rows} + + ) +} +class Product extends React.Component<{},ProductState> { + constructor(props) { + super(props); + this.state = { + product: [], + } + } + async componentDidMount() { + this.getProducts(); + } + async getProducts() { + try { + const client = new ProductClient(); + let data = await client.getAll(); + console.log(data); + this.setState({ + product: data + }); + } catch (error) { + this.setState({ + product: [], + }); + } + } + async deleteProduct(productId) { + try { + const client = new ProductClient(); + await client.delete(productId); + // Remove the product in the state by filter()ing it + let product = this.state.product.filter(x => x.productID != productId); + this.setState({ + product + }) + Swal.fire({ + title: "Deleted!", + text: "Product is deleted in database", + }) + } catch (error) { + alert(error); + } + } + render() { + return ( +
    +

    Products

    +
    ID Name Email
    {x.productID}{x.name}{x.price}
    + + + + + + + + + +
    IDNamePriceEditDelete
    + + + Create + + + + ); + } +} + +export default function ProductPage() { + return ( + + + + ) +} \ No newline at end of file diff --git a/TestAppRuna/Shop.sqlite.db b/TestAppRuna/Shop.sqlite.db index a4375204729c6137620b2f7789a0c62c7da3c828..77747a9bb6cf8db75063663ace7839a6ac4ad412 100644 GIT binary patch delta 478 zcma)&zfJ-{5XSe8lNg(USXhxGkpd)(yLT+Nmzc;dyB7-y7G7bc7d4R>|3t9Hg750|T&orq)^?icOZWu3XlKQg-+VLk`{rh9-%RcMd|~k!!82UIdR8n~Aobh{AcVH+ zbxDQZu$*`$eRS8dFL$}HWWyN23yh&{%nU}VzS?SqJZeP(OU47-^gWF^)gG2YDB%+k za7y>C`=?2{+aC_D&-&-bzVDOYYLIr$iE)XAig?TuSSiJElPZm6)MQwNfo7C(-I2PR z99@_t!>eR)Qa()1X-LR6Aq~Q9CoYu&!8lP;;K=V73>}16a4ij07zG}aN>aI-oF!)T zlCs(L23dl4WWxl(1n%I@BvYg%1*xHucGCfn34}v?2(yXnw*iT5K@#_ zoT1>Hm}g`F0(u5q3=9m6{5Kf*ZveHP<7ZK44&?-D)Bu{dfq{PmP;ePPyB`xPLue=? k$nwp%<26B+U1#9G4wO90&!WK$k(vCzo)wFPzX~H004|w7<^TWy