diff --git a/examples/kendo-react-redux-undo/.gitignore b/examples/kendo-react-redux-undo/.gitignore new file mode 100644 index 00000000..aae8560f --- /dev/null +++ b/examples/kendo-react-redux-undo/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +**/kendo-ui-license** + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# package-lock file +package-lock.json \ No newline at end of file diff --git a/examples/kendo-react-redux-undo/LICENSE b/examples/kendo-react-redux-undo/LICENSE new file mode 100644 index 00000000..01893653 --- /dev/null +++ b/examples/kendo-react-redux-undo/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Telerik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/kendo-react-redux-undo/README.md b/examples/kendo-react-redux-undo/README.md new file mode 100644 index 00000000..7f168c4b --- /dev/null +++ b/examples/kendo-react-redux-undo/README.md @@ -0,0 +1,13 @@ +# kendo-react-redux-undo +Kendo UI Grid using [Redux](https://redux.js.org/) for state management and [Redux Undo](https://github.com/omnidan/redux-undo) for history management. + +## Build Setup + +```bash +# install dependencies +npm install +# serve with hot reload at localhost:3000 +npm start +# build for production with minification +npm run build + diff --git a/examples/kendo-react-redux-undo/package.json b/examples/kendo-react-redux-undo/package.json new file mode 100644 index 00000000..5f07671c --- /dev/null +++ b/examples/kendo-react-redux-undo/package.json @@ -0,0 +1,33 @@ +{ + "name": "kendo-redux-timetravel-editing", + "version": "0.1.0", + "private": true, + "dependencies": { + "@progress/kendo-data-query": "^1.7.1", + "@progress/kendo-drawing": "^1.21.2", + "@progress/kendo-react-dateinputs": "^11.0.0", + "@progress/kendo-react-dropdowns": "^11.0.0", + "@progress/kendo-react-grid": "^11.0.0", + "@progress/kendo-react-inputs": "^11.0.0", + "@progress/kendo-react-intl": "^11.0.0", + "@progress/kendo-react-pdf": "^11.0.0", + "@progress/kendo-react-data-tools": "^11.0.0", + "@progress/kendo-theme-default": "^11.0.2", + "react": "^18", + "react-dom": "^18", + "react-redux": "^9.2.0", + "react-scripts": "5.0.1", + "redux": "^5.0.1", + "@redux-devtools/extension": "^3.3.0", + "@reduxjs/toolkit": "^2.8.2", + "redux-thunk": "^3.1.0", + "redux-undo": "^1.1.0", + "prop-types": "15.8.1" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/examples/kendo-react-redux-undo/public/favicon.ico b/examples/kendo-react-redux-undo/public/favicon.ico new file mode 100644 index 00000000..a11777cc Binary files /dev/null and b/examples/kendo-react-redux-undo/public/favicon.ico differ diff --git a/examples/kendo-react-redux-undo/public/index.html b/examples/kendo-react-redux-undo/public/index.html new file mode 100644 index 00000000..ed0ebafa --- /dev/null +++ b/examples/kendo-react-redux-undo/public/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + React App + + + +
+ + + diff --git a/examples/kendo-react-redux-undo/public/manifest.json b/examples/kendo-react-redux-undo/public/manifest.json new file mode 100644 index 00000000..ef19ec24 --- /dev/null +++ b/examples/kendo-react-redux-undo/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/kendo-react-redux-undo/src/App.css b/examples/kendo-react-redux-undo/src/App.css new file mode 100644 index 00000000..c5c6e8a6 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/App.css @@ -0,0 +1,28 @@ +.App { + text-align: center; +} + +.App-logo { + animation: App-logo-spin infinite 20s linear; + height: 80px; +} + +.App-header { + background-color: #222; + height: 150px; + padding: 20px; + color: white; +} + +.App-title { + font-size: 1.5em; +} + +.App-intro { + font-size: large; +} + +@keyframes App-logo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/examples/kendo-react-redux-undo/src/App.js b/examples/kendo-react-redux-undo/src/App.js new file mode 100644 index 00000000..90b3a7ed --- /dev/null +++ b/examples/kendo-react-redux-undo/src/App.js @@ -0,0 +1,17 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import './App.css'; +import GridContainer from './components/GridContainer'; +import '@progress/kendo-theme-default/dist/all.css'; + +class App extends Component { + render() { + return ( +
+ +
+ ); + } +} + +export default connect()(App); diff --git a/examples/kendo-react-redux-undo/src/App.test.js b/examples/kendo-react-redux-undo/src/App.test.js new file mode 100644 index 00000000..fb712258 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/App.test.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + const root = ReactDOM.createRoot(div); + root.render(); + root.unmount(); +}); diff --git a/examples/kendo-react-redux-undo/src/actions/actions.js b/examples/kendo-react-redux-undo/src/actions/actions.js new file mode 100644 index 00000000..a4d2bba3 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/actions/actions.js @@ -0,0 +1,23 @@ +export const ADD_PRODUCT = 'ADD_PRODUCT' +export const UPDATE_PRODUCT = 'UPDATE_PRODUCT' +export const DELETE_PRODUCT = 'DELETE_PRODUCT' +export const CHANGE_EDIT = 'CHANGE_EDIT' + +export const DATASTATE_CHANGE = 'DATASTATE_CHANGE' + +export function addProduct(payload) { + return { type: ADD_PRODUCT, payload } +} +export function updateProduct(payload) { + return { type: UPDATE_PRODUCT, payload } +} +export function deleteProduct(payload) { + return { type: DELETE_PRODUCT, payload } +} +export function changeEdit(payload) { + return { type: CHANGE_EDIT, payload } +} + +export function datastateChange(payload) { + return { type: DATASTATE_CHANGE, payload } +} diff --git a/examples/kendo-react-redux-undo/src/components/GridContainer.js b/examples/kendo-react-redux-undo/src/components/GridContainer.js new file mode 100644 index 00000000..0d736095 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/components/GridContainer.js @@ -0,0 +1,110 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { ActionCreators as UndoActionCreators } from 'redux-undo'; +import { addProduct, updateProduct, deleteProduct, changeEdit, datastateChange } from '../actions/actions'; +import { Grid, GridColumn as Column, GridToolbar, GridToolbarSort, GridToolbarFilter } from '@progress/kendo-react-grid'; +import { Button } from '@progress/kendo-react-buttons'; +import { gearIcon } from '@progress/kendo-svg-icons'; +import { process } from '@progress/kendo-data-query' +import MyCommandCell from './MyCommandCell' + +class GridContainer extends React.Component { + constructor(props) { + super(props); + this.CommandCell = MyCommandCell(this.remove); + } + + addItem = () => { + this.props.addProduct(); + } + + handleItemChange = (dataItem) => { + this.props.updateProduct(dataItem) + } + + handleStateChange = (event) => { + this.props.datastateChange(event) + } + + remove = (dataItem) => { + this.props.deleteProduct(dataItem); + } + + rowClick = (event) => { + this.props.changeEdit(event.dataItem); + } + + render() { + const processedProducts = process(this.props.products, this.props.dataState) + console.log(this.props.products.length) + return ( +
+ + + + + + + + + + + + + + +
+ ); + } +} + +const mapDispatchToProps = dispatch => { + return { + onUndo: () => dispatch(UndoActionCreators.undo()), + onRedo: () => dispatch(UndoActionCreators.redo()), + addProduct: () => dispatch(addProduct()), + updateProduct: (dataItem) => dispatch(updateProduct(dataItem)), + deleteProduct: (dataItem) => dispatch(deleteProduct(dataItem)), + changeEdit: (row) => dispatch(changeEdit(row)), + datastateChange: (event) => dispatch(datastateChange(event)) + } + } + +function mapStateToProps(state) { + return { + products: state.products.present.products, + dataState: state.dataState.present, + canUndo: state.products.past.length > 0, + canRedo: state.products.future.length > 0, + dataState: state.dataState.present + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(GridContainer); diff --git a/examples/kendo-react-redux-undo/src/components/MyCommandCell.js b/examples/kendo-react-redux-undo/src/components/MyCommandCell.js new file mode 100644 index 00000000..69329c91 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/components/MyCommandCell.js @@ -0,0 +1,17 @@ +import React from 'react'; + +export default function MyCommandCell(remove) { + return class extends React.Component { + render() { + return ( + + + + ); + } + } +}; \ No newline at end of file diff --git a/examples/kendo-react-redux-undo/src/data/products.js b/examples/kendo-react-redux-undo/src/data/products.js new file mode 100644 index 00000000..f603e1b4 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/data/products.js @@ -0,0 +1,1233 @@ +export const sampleProducts = [{ + "ProductID" : 1, + "ProductName" : "Chai", + "SupplierID" : 1, + "CategoryID" : 1, + "QuantityPerUnit" : "10 boxes x 20 bags", + "UnitPrice" : 18.0000, + "UnitsInStock" : 39, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 2, + "ProductName" : "Chang", + "SupplierID" : 1, + "CategoryID" : 1, + "QuantityPerUnit" : "24 - 12 oz bottles", + "UnitPrice" : 19.0000, + "UnitsInStock" : 17, + "UnitsOnOrder" : 40, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 3, + "ProductName" : "Aniseed Syrup", + "SupplierID" : 1, + "CategoryID" : 2, + "QuantityPerUnit" : "12 - 550 ml bottles", + "UnitPrice" : 10.0000, + "UnitsInStock" : 13, + "UnitsOnOrder" : 70, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 4, + "ProductName" : "Chef Anton's Cajun Seasoning", + "SupplierID" : 2, + "CategoryID" : 2, + "QuantityPerUnit" : "48 - 6 oz jars", + "UnitPrice" : 22.0000, + "UnitsInStock" : 53, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 5, + "ProductName" : "Chef Anton's Gumbo Mix", + "SupplierID" : 2, + "CategoryID" : 2, + "QuantityPerUnit" : "36 boxes", + "UnitPrice" : 21.3500, + "UnitsInStock" : 0, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 6, + "ProductName" : "Grandma's Boysenberry Spread", + "SupplierID" : 3, + "CategoryID" : 2, + "QuantityPerUnit" : "12 - 8 oz jars", + "UnitPrice" : 25.0000, + "UnitsInStock" : 120, + "UnitsOnOrder" : 0, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 7, + "ProductName" : "Uncle Bob's Organic Dried Pears", + "SupplierID" : 3, + "CategoryID" : 7, + "QuantityPerUnit" : "12 - 1 lb pkgs.", + "UnitPrice" : 30.0000, + "UnitsInStock" : 15, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 7, + "CategoryName" : "Produce", + "Description" : "Dried fruit and bean curd" + } +}, { + "ProductID" : 8, + "ProductName" : "Northwoods Cranberry Sauce", + "SupplierID" : 3, + "CategoryID" : 2, + "QuantityPerUnit" : "12 - 12 oz jars", + "UnitPrice" : 40.0000, + "UnitsInStock" : 6, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 9, + "ProductName" : "Mishi Kobe Niku", + "SupplierID" : 4, + "CategoryID" : 6, + "QuantityPerUnit" : "18 - 500 g pkgs.", + "UnitPrice" : 97.0000, + "UnitsInStock" : 29, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 6, + "CategoryName" : "Meat/Poultry", + "Description" : "Prepared meats" + } +}, { + "ProductID" : 10, + "ProductName" : "Ikura", + "SupplierID" : 4, + "CategoryID" : 8, + "QuantityPerUnit" : "12 - 200 ml jars", + "UnitPrice" : 31.0000, + "UnitsInStock" : 31, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 11, + "ProductName" : "Queso Cabrales", + "SupplierID" : 5, + "CategoryID" : 4, + "QuantityPerUnit" : "1 kg pkg.", + "UnitPrice" : 21.0000, + "UnitsInStock" : 22, + "UnitsOnOrder" : 30, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 12, + "ProductName" : "Queso Manchego La Pastora", + "SupplierID" : 5, + "CategoryID" : 4, + "QuantityPerUnit" : "10 - 500 g pkgs.", + "UnitPrice" : 38.0000, + "UnitsInStock" : 86, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 13, + "ProductName" : "Konbu", + "SupplierID" : 6, + "CategoryID" : 8, + "QuantityPerUnit" : "2 kg box", + "UnitPrice" : 6.0000, + "UnitsInStock" : 24, + "UnitsOnOrder" : 0, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 14, + "ProductName" : "Tofu", + "SupplierID" : 6, + "CategoryID" : 7, + "QuantityPerUnit" : "40 - 100 g pkgs.", + "UnitPrice" : 23.2500, + "UnitsInStock" : 35, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 7, + "CategoryName" : "Produce", + "Description" : "Dried fruit and bean curd" + } +}, { + "ProductID" : 15, + "ProductName" : "Genen Shouyu", + "SupplierID" : 6, + "CategoryID" : 2, + "QuantityPerUnit" : "24 - 250 ml bottles", + "UnitPrice" : 15.5000, + "UnitsInStock" : 39, + "UnitsOnOrder" : 0, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 16, + "ProductName" : "Pavlova", + "SupplierID" : 7, + "CategoryID" : 3, + "QuantityPerUnit" : "32 - 500 g boxes", + "UnitPrice" : 17.4500, + "UnitsInStock" : 29, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 17, + "ProductName" : "Alice Mutton", + "SupplierID" : 7, + "CategoryID" : 6, + "QuantityPerUnit" : "20 - 1 kg tins", + "UnitPrice" : 39.0000, + "UnitsInStock" : 0, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 6, + "CategoryName" : "Meat/Poultry", + "Description" : "Prepared meats" + } +}, { + "ProductID" : 18, + "ProductName" : "Carnarvon Tigers", + "SupplierID" : 7, + "CategoryID" : 8, + "QuantityPerUnit" : "16 kg pkg.", + "UnitPrice" : 62.5000, + "UnitsInStock" : 42, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 19, + "ProductName" : "Teatime Chocolate Biscuits", + "SupplierID" : 8, + "CategoryID" : 3, + "QuantityPerUnit" : "10 boxes x 12 pieces", + "UnitPrice" : 9.2000, + "UnitsInStock" : 25, + "UnitsOnOrder" : 0, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 20, + "ProductName" : "Sir Rodney's Marmalade", + "SupplierID" : 8, + "CategoryID" : 3, + "QuantityPerUnit" : "30 gift boxes", + "UnitPrice" : 81.0000, + "UnitsInStock" : 40, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 21, + "ProductName" : "Sir Rodney's Scones", + "SupplierID" : 8, + "CategoryID" : 3, + "QuantityPerUnit" : "24 pkgs. x 4 pieces", + "UnitPrice" : 10.0000, + "UnitsInStock" : 3, + "UnitsOnOrder" : 40, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 22, + "ProductName" : "Gustaf's Knäckebröd", + "SupplierID" : 9, + "CategoryID" : 5, + "QuantityPerUnit" : "24 - 500 g pkgs.", + "UnitPrice" : 21.0000, + "UnitsInStock" : 104, + "UnitsOnOrder" : 0, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 23, + "ProductName" : "Tunnbröd", + "SupplierID" : 9, + "CategoryID" : 5, + "QuantityPerUnit" : "12 - 250 g pkgs.", + "UnitPrice" : 9.0000, + "UnitsInStock" : 61, + "UnitsOnOrder" : 0, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 24, + "ProductName" : "Guaraná Fantástica", + "SupplierID" : 10, + "CategoryID" : 1, + "QuantityPerUnit" : "12 - 355 ml cans", + "UnitPrice" : 4.5000, + "UnitsInStock" : 20, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 25, + "ProductName" : "NuNuCa Nuß-Nougat-Creme", + "SupplierID" : 11, + "CategoryID" : 3, + "QuantityPerUnit" : "20 - 450 g glasses", + "UnitPrice" : 14.0000, + "UnitsInStock" : 76, + "UnitsOnOrder" : 0, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 26, + "ProductName" : "Gumbär Gummibärchen", + "SupplierID" : 11, + "CategoryID" : 3, + "QuantityPerUnit" : "100 - 250 g bags", + "UnitPrice" : 31.2300, + "UnitsInStock" : 15, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 27, + "ProductName" : "Schoggi Schokolade", + "SupplierID" : 11, + "CategoryID" : 3, + "QuantityPerUnit" : "100 - 100 g pieces", + "UnitPrice" : 43.9000, + "UnitsInStock" : 49, + "UnitsOnOrder" : 0, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 28, + "ProductName" : "Rössle Sauerkraut", + "SupplierID" : 12, + "CategoryID" : 7, + "QuantityPerUnit" : "25 - 825 g cans", + "UnitPrice" : 45.6000, + "UnitsInStock" : 26, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 7, + "CategoryName" : "Produce", + "Description" : "Dried fruit and bean curd" + } +}, { + "ProductID" : 29, + "ProductName" : "Thüringer Rostbratwurst", + "SupplierID" : 12, + "CategoryID" : 6, + "QuantityPerUnit" : "50 bags x 30 sausgs.", + "UnitPrice" : 123.7900, + "UnitsInStock" : 0, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 6, + "CategoryName" : "Meat/Poultry", + "Description" : "Prepared meats" + } +}, { + "ProductID" : 30, + "ProductName" : "Nord-Ost Matjeshering", + "SupplierID" : 13, + "CategoryID" : 8, + "QuantityPerUnit" : "10 - 200 g glasses", + "UnitPrice" : 25.8900, + "UnitsInStock" : 10, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 31, + "ProductName" : "Gorgonzola Telino", + "SupplierID" : 14, + "CategoryID" : 4, + "QuantityPerUnit" : "12 - 100 g pkgs", + "UnitPrice" : 12.5000, + "UnitsInStock" : 0, + "UnitsOnOrder" : 70, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 32, + "ProductName" : "Mascarpone Fabioli", + "SupplierID" : 14, + "CategoryID" : 4, + "QuantityPerUnit" : "24 - 200 g pkgs.", + "UnitPrice" : 32.0000, + "UnitsInStock" : 9, + "UnitsOnOrder" : 40, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 33, + "ProductName" : "Geitost", + "SupplierID" : 15, + "CategoryID" : 4, + "QuantityPerUnit" : "500 g", + "UnitPrice" : 2.5000, + "UnitsInStock" : 112, + "UnitsOnOrder" : 0, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 34, + "ProductName" : "Sasquatch Ale", + "SupplierID" : 16, + "CategoryID" : 1, + "QuantityPerUnit" : "24 - 12 oz bottles", + "UnitPrice" : 14.0000, + "UnitsInStock" : 111, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 35, + "ProductName" : "Steeleye Stout", + "SupplierID" : 16, + "CategoryID" : 1, + "QuantityPerUnit" : "24 - 12 oz bottles", + "UnitPrice" : 18.0000, + "UnitsInStock" : 20, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 36, + "ProductName" : "Inlagd Sill", + "SupplierID" : 17, + "CategoryID" : 8, + "QuantityPerUnit" : "24 - 250 g jars", + "UnitPrice" : 19.0000, + "UnitsInStock" : 112, + "UnitsOnOrder" : 0, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 37, + "ProductName" : "Gravad lax", + "SupplierID" : 17, + "CategoryID" : 8, + "QuantityPerUnit" : "12 - 500 g pkgs.", + "UnitPrice" : 26.0000, + "UnitsInStock" : 11, + "UnitsOnOrder" : 50, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 38, + "ProductName" : "Côte de Blaye", + "SupplierID" : 18, + "CategoryID" : 1, + "QuantityPerUnit" : "12 - 75 cl bottles", + "UnitPrice" : 263.5000, + "UnitsInStock" : 17, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 39, + "ProductName" : "Chartreuse verte", + "SupplierID" : 18, + "CategoryID" : 1, + "QuantityPerUnit" : "750 cc per bottle", + "UnitPrice" : 18.0000, + "UnitsInStock" : 69, + "UnitsOnOrder" : 0, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 40, + "ProductName" : "Boston Crab Meat", + "SupplierID" : 19, + "CategoryID" : 8, + "QuantityPerUnit" : "24 - 4 oz tins", + "UnitPrice" : 18.4000, + "UnitsInStock" : 123, + "UnitsOnOrder" : 0, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 41, + "ProductName" : "Jack's New England Clam Chowder", + "SupplierID" : 19, + "CategoryID" : 8, + "QuantityPerUnit" : "12 - 12 oz cans", + "UnitPrice" : 9.6500, + "UnitsInStock" : 85, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 42, + "ProductName" : "Singaporean Hokkien Fried Mee", + "SupplierID" : 20, + "CategoryID" : 5, + "QuantityPerUnit" : "32 - 1 kg pkgs.", + "UnitPrice" : 14.0000, + "UnitsInStock" : 26, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 43, + "ProductName" : "Ipoh Coffee", + "SupplierID" : 20, + "CategoryID" : 1, + "QuantityPerUnit" : "16 - 500 g tins", + "UnitPrice" : 46.0000, + "UnitsInStock" : 17, + "UnitsOnOrder" : 10, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 44, + "ProductName" : "Gula Malacca", + "SupplierID" : 20, + "CategoryID" : 2, + "QuantityPerUnit" : "20 - 2 kg bags", + "UnitPrice" : 19.4500, + "UnitsInStock" : 27, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 45, + "ProductName" : "Rogede sild", + "SupplierID" : 21, + "CategoryID" : 8, + "QuantityPerUnit" : "1k pkg.", + "UnitPrice" : 9.5000, + "UnitsInStock" : 5, + "UnitsOnOrder" : 70, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 46, + "ProductName" : "Spegesild", + "SupplierID" : 21, + "CategoryID" : 8, + "QuantityPerUnit" : "4 - 450 g glasses", + "UnitPrice" : 12.0000, + "UnitsInStock" : 95, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 47, + "ProductName" : "Zaanse koeken", + "SupplierID" : 22, + "CategoryID" : 3, + "QuantityPerUnit" : "10 - 4 oz boxes", + "UnitPrice" : 9.5000, + "UnitsInStock" : 36, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 48, + "ProductName" : "Chocolade", + "SupplierID" : 22, + "CategoryID" : 3, + "QuantityPerUnit" : "10 pkgs.", + "UnitPrice" : 12.7500, + "UnitsInStock" : 15, + "UnitsOnOrder" : 70, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 49, + "ProductName" : "Maxilaku", + "SupplierID" : 23, + "CategoryID" : 3, + "QuantityPerUnit" : "24 - 50 g pkgs.", + "UnitPrice" : 20.0000, + "UnitsInStock" : 10, + "UnitsOnOrder" : 60, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 50, + "ProductName" : "Valkoinen suklaa", + "SupplierID" : 23, + "CategoryID" : 3, + "QuantityPerUnit" : "12 - 100 g bars", + "UnitPrice" : 16.2500, + "UnitsInStock" : 65, + "UnitsOnOrder" : 0, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 51, + "ProductName" : "Manjimup Dried Apples", + "SupplierID" : 24, + "CategoryID" : 7, + "QuantityPerUnit" : "50 - 300 g pkgs.", + "UnitPrice" : 53.0000, + "UnitsInStock" : 20, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 7, + "CategoryName" : "Produce", + "Description" : "Dried fruit and bean curd" + } +}, { + "ProductID" : 52, + "ProductName" : "Filo Mix", + "SupplierID" : 24, + "CategoryID" : 5, + "QuantityPerUnit" : "16 - 2 kg boxes", + "UnitPrice" : 7.0000, + "UnitsInStock" : 38, + "UnitsOnOrder" : 0, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 53, + "ProductName" : "Perth Pasties", + "SupplierID" : 24, + "CategoryID" : 6, + "QuantityPerUnit" : "48 pieces", + "UnitPrice" : 32.8000, + "UnitsInStock" : 0, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : true, + "Category" : { + "CategoryID" : 6, + "CategoryName" : "Meat/Poultry", + "Description" : "Prepared meats" + } +}, { + "ProductID" : 54, + "ProductName" : "Tourtière", + "SupplierID" : 25, + "CategoryID" : 6, + "QuantityPerUnit" : "16 pies", + "UnitPrice" : 7.4500, + "UnitsInStock" : 21, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 6, + "CategoryName" : "Meat/Poultry", + "Description" : "Prepared meats" + } +}, { + "ProductID" : 55, + "ProductName" : "Pâté chinois", + "SupplierID" : 25, + "CategoryID" : 6, + "QuantityPerUnit" : "24 boxes x 2 pies", + "UnitPrice" : 24.0000, + "UnitsInStock" : 115, + "UnitsOnOrder" : 0, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 6, + "CategoryName" : "Meat/Poultry", + "Description" : "Prepared meats" + } +}, { + "ProductID" : 56, + "ProductName" : "Gnocchi di nonna Alice", + "SupplierID" : 26, + "CategoryID" : 5, + "QuantityPerUnit" : "24 - 250 g pkgs.", + "UnitPrice" : 38.0000, + "UnitsInStock" : 21, + "UnitsOnOrder" : 10, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 57, + "ProductName" : "Ravioli Angelo", + "SupplierID" : 26, + "CategoryID" : 5, + "QuantityPerUnit" : "24 - 250 g pkgs.", + "UnitPrice" : 19.5000, + "UnitsInStock" : 36, + "UnitsOnOrder" : 0, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 58, + "ProductName" : "Escargots de Bourgogne", + "SupplierID" : 27, + "CategoryID" : 8, + "QuantityPerUnit" : "24 pieces", + "UnitPrice" : 13.2500, + "UnitsInStock" : 62, + "UnitsOnOrder" : 0, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 59, + "ProductName" : "Raclette Courdavault", + "SupplierID" : 28, + "CategoryID" : 4, + "QuantityPerUnit" : "5 kg pkg.", + "UnitPrice" : 55.0000, + "UnitsInStock" : 79, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 60, + "ProductName" : "Camembert Pierrot", + "SupplierID" : 28, + "CategoryID" : 4, + "QuantityPerUnit" : "15 - 300 g rounds", + "UnitPrice" : 34.0000, + "UnitsInStock" : 19, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 61, + "ProductName" : "Sirop d'érable", + "SupplierID" : 29, + "CategoryID" : 2, + "QuantityPerUnit" : "24 - 500 ml bottles", + "UnitPrice" : 28.5000, + "UnitsInStock" : 113, + "UnitsOnOrder" : 0, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 62, + "ProductName" : "Tarte au sucre", + "SupplierID" : 29, + "CategoryID" : 3, + "QuantityPerUnit" : "48 pies", + "UnitPrice" : 49.3000, + "UnitsInStock" : 17, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 63, + "ProductName" : "Vegie-spread", + "SupplierID" : 7, + "CategoryID" : 2, + "QuantityPerUnit" : "15 - 625 g jars", + "UnitPrice" : 43.9000, + "UnitsInStock" : 24, + "UnitsOnOrder" : 0, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 64, + "ProductName" : "Wimmers gute Semmelknödel", + "SupplierID" : 12, + "CategoryID" : 5, + "QuantityPerUnit" : "20 bags x 4 pieces", + "UnitPrice" : 33.2500, + "UnitsInStock" : 22, + "UnitsOnOrder" : 80, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 5, + "CategoryName" : "Grains/Cereals", + "Description" : "Breads, crackers, pasta, and cereal" + } +}, { + "ProductID" : 65, + "ProductName" : "Louisiana Fiery Hot Pepper Sauce", + "SupplierID" : 2, + "CategoryID" : 2, + "QuantityPerUnit" : "32 - 8 oz bottles", + "UnitPrice" : 21.0500, + "UnitsInStock" : 76, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 66, + "ProductName" : "Louisiana Hot Spiced Okra", + "SupplierID" : 2, + "CategoryID" : 2, + "QuantityPerUnit" : "24 - 8 oz jars", + "UnitPrice" : 17.0000, + "UnitsInStock" : 4, + "UnitsOnOrder" : 100, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}, { + "ProductID" : 67, + "ProductName" : "Laughing Lumberjack Lager", + "SupplierID" : 16, + "CategoryID" : 1, + "QuantityPerUnit" : "24 - 12 oz bottles", + "UnitPrice" : 14.0000, + "UnitsInStock" : 52, + "UnitsOnOrder" : 0, + "ReorderLevel" : 10, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 68, + "ProductName" : "Scottish Longbreads", + "SupplierID" : 8, + "CategoryID" : 3, + "QuantityPerUnit" : "10 boxes x 8 pieces", + "UnitPrice" : 12.5000, + "UnitsInStock" : 6, + "UnitsOnOrder" : 10, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 3, + "CategoryName" : "Confections", + "Description" : "Desserts, candies, and sweet breads" + } +}, { + "ProductID" : 69, + "ProductName" : "Gudbrandsdalsost", + "SupplierID" : 15, + "CategoryID" : 4, + "QuantityPerUnit" : "10 kg pkg.", + "UnitPrice" : 36.0000, + "UnitsInStock" : 26, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 70, + "ProductName" : "Outback Lager", + "SupplierID" : 7, + "CategoryID" : 1, + "QuantityPerUnit" : "24 - 355 ml bottles", + "UnitPrice" : 15.0000, + "UnitsInStock" : 15, + "UnitsOnOrder" : 10, + "ReorderLevel" : 30, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 71, + "ProductName" : "Flotemysost", + "SupplierID" : 15, + "CategoryID" : 4, + "QuantityPerUnit" : "10 - 500 g pkgs.", + "UnitPrice" : 21.5000, + "UnitsInStock" : 26, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 72, + "ProductName" : "Mozzarella di Giovanni", + "SupplierID" : 14, + "CategoryID" : 4, + "QuantityPerUnit" : "24 - 200 g pkgs.", + "UnitPrice" : 34.8000, + "UnitsInStock" : 14, + "UnitsOnOrder" : 0, + "ReorderLevel" : 0, + "Discontinued" : false, + "Category" : { + "CategoryID" : 4, + "CategoryName" : "Dairy Products", + "Description" : "Cheeses" + } +}, { + "ProductID" : 73, + "ProductName" : "Röd Kaviar", + "SupplierID" : 17, + "CategoryID" : 8, + "QuantityPerUnit" : "24 - 150 g jars", + "UnitPrice" : 15.0000, + "UnitsInStock" : 101, + "UnitsOnOrder" : 0, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 8, + "CategoryName" : "Seafood", + "Description" : "Seaweed and fish" + } +}, { + "ProductID" : 74, + "ProductName" : "Longlife Tofu", + "SupplierID" : 4, + "CategoryID" : 7, + "QuantityPerUnit" : "5 kg pkg.", + "UnitPrice" : 10.0000, + "UnitsInStock" : 4, + "UnitsOnOrder" : 20, + "ReorderLevel" : 5, + "Discontinued" : false, + "Category" : { + "CategoryID" : 7, + "CategoryName" : "Produce", + "Description" : "Dried fruit and bean curd" + } +}, { + "ProductID" : 75, + "ProductName" : "Rhönbräu Klosterbier", + "SupplierID" : 12, + "CategoryID" : 1, + "QuantityPerUnit" : "24 - 0.5 l bottles", + "UnitPrice" : 7.7500, + "UnitsInStock" : 125, + "UnitsOnOrder" : 0, + "ReorderLevel" : 25, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 76, + "ProductName" : "Lakkalikööri", + "SupplierID" : 23, + "CategoryID" : 1, + "QuantityPerUnit" : "500 ml", + "UnitPrice" : 18.0000, + "UnitsInStock" : 57, + "UnitsOnOrder" : 0, + "ReorderLevel" : 20, + "Discontinued" : false, + "Category" : { + "CategoryID" : 1, + "CategoryName" : "Beverages", + "Description" : "Soft drinks, coffees, teas, beers, and ales" + } +}, { + "ProductID" : 77, + "ProductName" : "Original Frankfurter grüne Soße", + "SupplierID" : 12, + "CategoryID" : 2, + "QuantityPerUnit" : "12 boxes", + "UnitPrice" : 13.0000, + "UnitsInStock" : 32, + "UnitsOnOrder" : 0, + "ReorderLevel" : 15, + "Discontinued" : false, + "Category" : { + "CategoryID" : 2, + "CategoryName" : "Condiments", + "Description" : "Sweet and savory sauces, relishes, spreads, and seasonings" + } +}]; diff --git a/examples/kendo-react-redux-undo/src/index.css b/examples/kendo-react-redux-undo/src/index.css new file mode 100644 index 00000000..6c39d0eb --- /dev/null +++ b/examples/kendo-react-redux-undo/src/index.css @@ -0,0 +1,9 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} + +.button-right { + float: left; +} diff --git a/examples/kendo-react-redux-undo/src/index.js b/examples/kendo-react-redux-undo/src/index.js new file mode 100644 index 00000000..ff78f977 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import './index.css'; +import App from './App'; +import { Provider } from 'react-redux'; +import { store } from './reducers/index'; +import { createRoot } from 'react-dom/client'; + +const container = document.getElementById('root'); +const root = createRoot(container); + +root.render( + + + +); \ No newline at end of file diff --git a/examples/kendo-react-redux-undo/src/logo.svg b/examples/kendo-react-redux-undo/src/logo.svg new file mode 100644 index 00000000..6b60c104 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/kendo-react-redux-undo/src/reducers/index.js b/examples/kendo-react-redux-undo/src/reducers/index.js new file mode 100644 index 00000000..335296fd --- /dev/null +++ b/examples/kendo-react-redux-undo/src/reducers/index.js @@ -0,0 +1,12 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { combineReducers } from 'redux'; +import { undoableProducts, undoableDataState } from './reducers'; + +const rootReducer = combineReducers({ + products: undoableProducts, + dataState: undoableDataState, +}); + +export const store = configureStore({ + reducer: rootReducer +}); \ No newline at end of file diff --git a/examples/kendo-react-redux-undo/src/reducers/reducers.js b/examples/kendo-react-redux-undo/src/reducers/reducers.js new file mode 100644 index 00000000..0f6b3668 --- /dev/null +++ b/examples/kendo-react-redux-undo/src/reducers/reducers.js @@ -0,0 +1,74 @@ +import { + ADD_PRODUCT, + UPDATE_PRODUCT, + DELETE_PRODUCT, + CHANGE_EDIT, + DATASTATE_CHANGE + } from '../actions/actions' +import { sampleProducts } from '../data/products' +import undoable from 'redux-undo' + +export function productsReducer(state = {products: sampleProducts}, action) { + switch (action.type) { + + case ADD_PRODUCT: + let makeNewID = Math.random() * new Date().getMilliseconds() * 100 + let newProductID = Math.floor(makeNewID) + let productForAdd = { inEdit: true, ProductID: newProductID }; + let productsAfterAdd = state.products.map(product => ({ + ...product, + inEdit: false + })); + productsAfterAdd.unshift(productForAdd) + return { + ...state, + products: productsAfterAdd + }; + + case UPDATE_PRODUCT: + const newProducts = state.products.map(product => { + const newProduct = { + ...product + }; + if(product.ProductID === action.payload.dataItem.ProductID) { + newProduct[action.payload.field] = action.payload.value; + } + return newProduct; + }) + return Object.assign({ products: newProducts }, {}); + + case DELETE_PRODUCT: + let index = state.products.findIndex(product => product.ProductID === action.payload.ProductID); + let productsAfterDelete = state.products.slice() + productsAfterDelete.splice(index, 1) + return Object.assign({ products: productsAfterDelete }, {}); + case CHANGE_EDIT: + const newProductsSelection = state.products.map(product => { + const newProduct = { + ...product + }; + if(product.ProductID === action.payload.ProductID) { + newProduct.inEdit = true + } else { + newProduct.inEdit = false + } + return newProduct; + }) + return Object.assign({ products: newProductsSelection }, {}); + default: + return state + } +} + +export function gridStateReducer(state = {sort: [], filter:[], skip: 0, take: 10}, action) { + switch (action.type) { + case DATASTATE_CHANGE: + const dataState = action.payload.data + return Object.assign({ sort: dataState.sort, filter: dataState.filter, skip: dataState.skip, take: dataState.take }, {}); + default: + return state + } +} + +export const undoableProducts = undoable(productsReducer) +export const undoableDataState = undoable(gridStateReducer) \ No newline at end of file diff --git a/examples/kendo-react-redux-undo/src/registerServiceWorker.js b/examples/kendo-react-redux-undo/src/registerServiceWorker.js new file mode 100644 index 00000000..a3e6c0cf --- /dev/null +++ b/examples/kendo-react-redux-undo/src/registerServiceWorker.js @@ -0,0 +1,117 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://goo.gl/SC7cgQ' + ); + }); + } else { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +}