diff --git a/examples/finefoods-antd/public/images/login-bg.png b/examples/finefoods-antd/public/images/login-bg.png index c789aed204af..a9ad07b3fdb5 100644 Binary files a/examples/finefoods-antd/public/images/login-bg.png and b/examples/finefoods-antd/public/images/login-bg.png differ diff --git a/examples/finefoods-antd/src/components/store/map/all-stores-map.tsx b/examples/finefoods-antd/src/components/store/map/all-stores-map.tsx index 28d8636227fc..e4bc974331a0 100644 --- a/examples/finefoods-antd/src/components/store/map/all-stores-map.tsx +++ b/examples/finefoods-antd/src/components/store/map/all-stores-map.tsx @@ -22,10 +22,8 @@ export const AllStoresMap = () => { const { data: storeData } = useList({ resource: "stores", - config: { - pagination: { - mode: "off", - }, + pagination: { + mode: "off", }, }); const stores = storeData?.data || []; diff --git a/examples/finefoods-material-ui/package.json b/examples/finefoods-material-ui/package.json index 5451a8921d11..b0f464835b7b 100644 --- a/examples/finefoods-material-ui/package.json +++ b/examples/finefoods-material-ui/package.json @@ -24,6 +24,7 @@ "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", "@googlemaps/react-wrapper": "^1.1.35", + "@mui/icons-material": "^5.8.3", "@mui/lab": "^5.0.0-alpha.85", "@mui/material": "^5.14.2", "@mui/x-data-grid": "^6.6.0", @@ -49,7 +50,8 @@ "react-i18next": "^11.8.11", "react-input-mask": "^2.0.4", "react-router-dom": "^6.8.1", - "recharts": "^2.1.9" + "recharts": "^2.1.9", + "usehooks-ts": "^2.14.0" }, "devDependencies": { "@types/google.maps": "^3.50.4", diff --git a/examples/finefoods-material-ui/public/images/courier-default-avatar.png b/examples/finefoods-material-ui/public/images/courier-default-avatar.png new file mode 100644 index 000000000000..1052b92cd1a3 Binary files /dev/null and b/examples/finefoods-material-ui/public/images/courier-default-avatar.png differ diff --git a/examples/finefoods-material-ui/public/images/fine-foods-login.svg b/examples/finefoods-material-ui/public/images/fine-foods-login.svg index 9a1d31b32c51..1ac8c0ff311d 100644 --- a/examples/finefoods-material-ui/public/images/fine-foods-login.svg +++ b/examples/finefoods-material-ui/public/images/fine-foods-login.svg @@ -1,11 +1,4 @@ - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/examples/finefoods-material-ui/public/images/login-bg.png b/examples/finefoods-material-ui/public/images/login-bg.png index c789aed204af..a9ad07b3fdb5 100644 Binary files a/examples/finefoods-material-ui/public/images/login-bg.png and b/examples/finefoods-material-ui/public/images/login-bg.png differ diff --git a/examples/finefoods-material-ui/public/images/marker-courier.svg b/examples/finefoods-material-ui/public/images/marker-courier.svg index 1ebd4820dc9f..f5088e14a6dc 100644 --- a/examples/finefoods-material-ui/public/images/marker-courier.svg +++ b/examples/finefoods-material-ui/public/images/marker-courier.svg @@ -1,4 +1,5 @@ - + + + + + \ No newline at end of file diff --git a/examples/finefoods-material-ui/public/images/marker-customer.svg b/examples/finefoods-material-ui/public/images/marker-customer.svg new file mode 100644 index 000000000000..27bea1c949ee --- /dev/null +++ b/examples/finefoods-material-ui/public/images/marker-customer.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/finefoods-material-ui/public/images/marker-location.svg b/examples/finefoods-material-ui/public/images/marker-location.svg deleted file mode 100644 index 35f1eaeac402..000000000000 --- a/examples/finefoods-material-ui/public/images/marker-location.svg +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/examples/finefoods-material-ui/public/images/marker-store-pick.svg b/examples/finefoods-material-ui/public/images/marker-store-pick.svg new file mode 100644 index 000000000000..96ca38619636 --- /dev/null +++ b/examples/finefoods-material-ui/public/images/marker-store-pick.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/finefoods-material-ui/public/images/marker-store.svg b/examples/finefoods-material-ui/public/images/marker-store.svg new file mode 100644 index 000000000000..5d906684ba64 --- /dev/null +++ b/examples/finefoods-material-ui/public/images/marker-store.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/finefoods-material-ui/public/images/product-default-img.png b/examples/finefoods-material-ui/public/images/product-default-img.png index 7e4cde2470cc..25569be22064 100644 Binary files a/examples/finefoods-material-ui/public/images/product-default-img.png and b/examples/finefoods-material-ui/public/images/product-default-img.png differ diff --git a/examples/finefoods-material-ui/public/images/upload-button-overlay-circle.png b/examples/finefoods-material-ui/public/images/upload-button-overlay-circle.png new file mode 100644 index 000000000000..dc895f1fd476 Binary files /dev/null and b/examples/finefoods-material-ui/public/images/upload-button-overlay-circle.png differ diff --git a/examples/finefoods-material-ui/public/locales/de.json b/examples/finefoods-material-ui/public/locales/de.json index cb11e33e93bc..69021523f53b 100644 --- a/examples/finefoods-material-ui/public/locales/de.json +++ b/examples/finefoods-material-ui/public/locales/de.json @@ -69,7 +69,9 @@ "list": "Aufführen", "create": "Erstellen", "edit": "Bearbeiten", - "show": "Zeigen" + "show": "Zeigen", + "delete": "Löschen", + "cancel": "Abbrechen" }, "buttons": { "create": "Erstellen", @@ -99,7 +101,8 @@ "clone": "Klonen" }, "table": { - "actions": "Aktionen" + "actions": "Aktionen", + "inTotal": "Insgesamt" }, "search": { "placeholder": "Suchen mit Geschäfts-ID, E-mail oder Stichwort", @@ -129,7 +132,12 @@ "fields": { "id": "ID", "title": "Titel", - "isActive": "Aktiv" + "products": "Produkte", + "isActive": { + "label": "Aktiv", + "true": "Aktiv", + "false": "Passiv" + } } }, "couriers": { @@ -143,41 +151,124 @@ "id": "ID", "avatar": { "label": "Avatar", - "description": "Ein Foto hochladen." + "description": "Klicken oder ziehen Sie das Avatarbild in diesen Bereich, um es hochzuladen." + }, + "name": { + "label": "Name", + "placeholder": "Bitte Kuriername eingeben" + }, + "surname": { + "label": "Nachname", + "placeholder": "Bitte Kurier-Nachname eingeben" }, - "name": "Vorname", - "surname": "Nachname", "gender": { "label": "Geschlecht", - "male": "Mann", - "female": "Frau" + "male": "Männlich", + "female": "Weiblich" + }, + "gsm": { + "label": "GSM", + "placeholder": "Bitte Kurier-GSM eingeben" + }, + "email": { + "label": "E-Mail", + "placeholder": "Bitte Kurier-E-Mail eingeben" + }, + "address": { + "label": "Adresse", + "placeholder": "Bitte Kurieradresse eingeben" }, - "gsm": "Gsm", "createdAt": "Erstellt am", - "isActive": "Aktiv", + "isActive": { + "label": "Status", + "Available": "Verfügbar", + "Offline": "Offline", + "On delivery": "Bei Zustellung" + }, + "vehicleType": { + "label": "Fahrzeugtyp", + "placeholder": "Fahrzeugtyp auswählen" + }, + "licensePlate": { + "label": "Kennzeichen", + "placeholder": "Bitte Kennzeichen eingeben", + "patternMessage": "Bitte geben Sie eine gültige Fahrzeug-ID ein. Beispiel: ABC 123" + }, + "rating": { + "label": "Bewertung", + "placeholder": "Bitte Bewertung eingeben" + }, "images": { - "label": "Foto", - "description": "Ein Foto hochladen.", - "validation": "Fotoauflösung: 480x480px" + "label": "Bilder", + "description": "Benutzerbild hinzufügen", + "validation": "muss 480x480 px sein" }, - "store": "Geschäft", - "vehicle": "Fahrzeug", - "accountNumber": "Kontonummer", - "address": "Adresse" + "store": { + "label": "Geschäft", + "placeholder": "Geschäft auswählen" + }, + "vehicle": { + "label": "Fahrzeug", + "placeholder": "Fahrzeug auswählen" + }, + "accountNumber": { + "label": "Kontonummer", + "placeholder": "Bitte Kontonummer eingeben" + } + }, + "filter": { + "title": "Filter", + "name": { + "label": "Name", + "placeholder": "Name suchen" + }, + "licensePlate": { + "label": "Kennzeichen", + "placeholder": "Kennzeichen suchen" + }, + "gsm": { + "label": "GSM", + "placeholder": "GSM suchen" + }, + "email": { + "label": "E-Mail", + "placeholder": "E-Mail suchen" + }, + "submit": "Filtern" }, "steps": { - "content": "Inhalt", - "relations": "Beziehungen" + "personal": "Persönlich", + "company": "Unternehmen", + "vehicle": "Fahrzeug" + }, + "actions": { + "add": "Neuen Kurier hinzufügen", + "edit": "Kurier bearbeiten" } }, + "dashboard": { "title": "Panel", + "overview": { + "title": "Übersicht" + }, + "filter": { + "title": "Filter", + "date": { + "lastWeek": "Letzte Woche", + "lastMonth": "Letzter Monat", + "lastYear": "Letztes Jahr" + } + }, "deliveryMap": { "title": "Lieferkarte" }, "orders": { "title": "Bestellungen" }, + "revenue": { + "title": "Umsatz" + }, "totalSales": { "title": "Gesamtumsatz" }, @@ -205,6 +296,9 @@ }, "trendingMenus": { "title": "Täglich beliebte Menüs" + }, + "trendingProducts": { + "title": "Täglich Produkte" } }, "enum": { @@ -218,9 +312,13 @@ }, "orders": { "orders": "Bestellungen", + "order": "Bestellung", "titles": { + "deliveryMap": "Lieferkarte", + "deliveryDetails": "Lieferdetails", + "products": "Produkte", "list": "Bestellungen", - "show": "Bestellung Info" + "show": "Bestellung" }, "fields": { "orderNumber": "Bestellungsnummer", @@ -231,7 +329,12 @@ "products": "Produkte", "createdAt": "Erstellt am", "amount": "Betrag", - "itemsAmount": "{{amount}} Produkt" + "itemsAmount": "{{amount}} Produkt", + "order": "Bestellung", + "customer": "Kunde", + "courier": "Kurier", + "phone": "Telefon", + "deliveryTime": "Lieferzeit" }, "filter": { "title": "Filter", @@ -251,9 +354,17 @@ "label": "Benutzer", "placeholder": "Benutzer Suchen" }, + "customer": { + "label": "Kunde", + "placeholder": "Kunde Suchen" + }, "createdAt": { "label": "Erstellt am" }, + "orderNumber": { + "label": "Bestellungsnummer", + "placeholder": "Bestellungsnummer" + }, "submit": "Filter" }, "courier": { @@ -273,7 +384,6 @@ }, "products": { "products": "Produkte", - "noProducts": "Keine Produkte", "titles": { "list": "Produkte", "create": "Produkt erstellen", @@ -283,7 +393,11 @@ "id": "ID", "name": "Titel", "description": "Bezeichnung", - "isActive": "Aktiv", + "isActive": { + "label": "Aktiv", + "true": "Aktiv", + "false": "Passiv" + }, "price": "Price", "category": "Kategorie", "images": { @@ -292,6 +406,42 @@ "validation": "Fotoauflösung: 1080x1080px." }, "createdAt": "Erstellt am" + }, + "filter": { + "title": "Filter", + "allCategories": { + "label": "Alle Kategorien", + "placeholder": "Alle Kategorien" + }, + "id": { + "label": "ID", + "placeholder": "ID suchen" + }, + "name": { + "label": "Name", + "placeholder": "Name suchen" + }, + "description": { + "label": "Beschreibung", + "placeholder": "Beschreibung suchen" + }, + "category": { + "label": "Kategorie", + "placeholder": "Kategorie suchen" + }, + "createdAt": { + "label": "Erstellt am" + }, + "isActive": { + "label": "Status", + "placeholder": "Status auswählen", + "true": "Verfügbar", + "false": "Nicht verfügbar" + } + }, + "actions": { + "add": "Neues Produkt hinzufügen", + "edit": "Produkt bearbeiten" } }, "reviews": { @@ -317,12 +467,49 @@ "fields": { "id": "Geschäfts-ID", "title": "Titel", - "isActive": "Aktiv", + "isActive": { + "label": "Status", + "placeholder": "Status auswählen", + "true": "Aktiv", + "false": "Passiv" + }, "createdAt": "Erstellt am", "address": "Adresse", "email": "Email", "gsm": "Telefon" }, + "filter": { + "id": { + "label": "ID", + "placeholder": "ID suchen" + }, + "title": { + "label": "Titel", + "placeholder": "Titel suchen" + }, + "email": { + "label": "E-Mail", + "placeholder": "E-Mail suchen" + }, + "gsm": { + "label": "Gsm", + "placeholder": "Gsm suchen" + }, + "search": { + "label": "Suche", + "placeholder": "Vorname, Nachname, Gsm, usw." + }, + "createdAt": { + "label": "Erstellt am" + }, + "isActive": { + "label": "Status", + "placeholder": "Status auswählen", + "true": "Offen", + "false": "Geschlossen" + }, + "submit": "Filtern" + }, "buttons": { "editProducts": "Produkte bearbeiten", "addProduct": "Produkt erstellen", @@ -333,14 +520,16 @@ "storeProducts": "Produkte des Geschäftes", "tagFilterDescription": "Filter mit Etiketten", "all": "Alles", - "productSearch": "Produkt suchen" + "productSearch": "Produkt suchen", + "addNewStore": "Neues Geschäft hinzufügen" }, "users": { - "users": "Benutzern", + "users": "Kunden", + "resourceLabel": "Kunden", "titles": { - "edit": "Benutzer bearbeiten", - "list": "Benutzern", - "show": "Benutzer Information" + "edit": "Kunden bearbeiten", + "list": "Kunden", + "show": "Kunden Information" }, "fields": { "id": "ID", @@ -350,21 +539,35 @@ }, "firstName": "Vorname", "lastName": "Nachname", + "name": "Name", "gender": { "label": "Geschlecht", "Male": "Mann", "Female": "Frau" }, - "gsm": "GSM", - "createdAt": "Erstellt", + "gsm": "GSM Num", + "createdAt": "Erstellt am", + "addresses": "Adressen", "isActive": { - "label": "Aktiv", - "true": "Ja", - "false": "Nein" + "label": "Status", + "true": "Aktiv", + "false": "Passiv" } }, "filter": { "title": "Filter", + "id": { + "label": "ID", + "placeholder": "ID" + }, + "name": { + "label": "Name", + "placeholder": "Name" + }, + "gsm": { + "label": "GSM", + "placeholder": "GSM" + }, "search": { "label": "Suche", "placeholder": "Vorname, Nachname, GSM..." @@ -393,11 +596,7 @@ }, "errors": { "required": { - "field": "'{{ field }}' ist erforderlich", - "common": "Dieses Feld wird benötigt", - "min": "Dieses Feld muss mindestens {{ min }} Zeichen lang sein", - "max": "Dieses Feld darf höchstens {{ max }} Zeichen lang sein", - "invalidMail": "Dieses Feld muss eine gültige E-Mail-Adresse sein" + "field": "Dieses Feld ist erforderlich." } } } diff --git a/examples/finefoods-material-ui/public/locales/en.json b/examples/finefoods-material-ui/public/locales/en.json index 871e9f8485cf..8a080cf6d063 100644 --- a/examples/finefoods-material-ui/public/locales/en.json +++ b/examples/finefoods-material-ui/public/locales/en.json @@ -69,7 +69,9 @@ "list": "List", "create": "Create", "edit": "Edit", - "show": "Show" + "show": "Show", + "delete": "Delete", + "cancel": "Cancel" }, "buttons": { "add": "Add", @@ -93,17 +95,18 @@ "reject": "Reject", "rejectAll": "Reject All", "nextStep": "Next", - "previousStep": "Previous" + "previousStep": "Prev" }, "loading": "Loading", "tags": { "clone": "Clone" }, "table": { - "actions": "Actions" + "actions": "Actions", + "inTotal": "in total" }, "search": { - "placeholder": "Search by Store ID, E-mail, Keyword", + "placeholder": "Search by Store ID,E-mail,Keyword", "more": "more" }, "status": { @@ -131,7 +134,12 @@ "fields": { "id": "ID", "title": "Title", - "isActive": "Active" + "products": "Products", + "isActive": { + "label": "Status", + "true": "Visible", + "false": "Invisible" + } } }, "couriers": { @@ -147,40 +155,121 @@ "label": "Avatar", "description": "Click or drag avatar image to this area to upload." }, - "name": "Name", - "surname": "Surname", + "name": { + "label": "Name", + "placeholder": "Please enter courier name" + }, + "surname": { + "label": "Surname", + "placeholder": "Please enter courier surname" + }, "gender": { "label": "Gender", "male": "Male", "female": "Female" }, - "gsm": "Phone", - "email": "E-Mail", - "address": "Address", - "createdAt": "CreatedAt", - "isActive": "Is Active", + "gsm": { + "label": "Gsm", + "placeholder": "Please enter courier gsm" + }, + "email": { + "label": "Email", + "placeholder": "Please enter courier email" + }, + "address": { + "label": "Address", + "placeholder": "Please enter courier address" + }, + "createdAt": "Created At", + "status": { + "label": "Status", + "Available": "Available", + "Offline": "Offline", + "On delivery": "On delivery" + }, + "vehicleType": { + "label": "Vehicle Type", + "placeholder": "Please enter Vehicle Type" + }, + "licensePlate": { + "label": "Vehicle ID", + "placeholder": "Please enter Vehicle ID", + "patternMessage": "Please enter valid Vehicle ID. Example: ABC 123" + }, + "rating": { + "label": "Rating", + "placeholder": "Please enter rating" + }, "images": { "label": "Images", "description": "Add user picture", "validation": "must be 480x480 px" }, - "store": "Store", - "vehicle": "Vehicle", - "accountNumber": "Account Number" + "store": { + "label": "Store", + "placeholder": "Select Store" + }, + "vehicle": { + "label": "Vehicle", + "placeholder": "Select Vehicle" + }, + "accountNumber": { + "label": "Account No", + "placeholder": "Please enter account no" + } + }, + "filter": { + "title": "Filters", + "name": { + "label": "Name", + "placeholder": "Search Name" + }, + "licensePlate": { + "label": "Name", + "placeholder": "Search Name" + }, + "gsm": { + "label": "Gsm", + "placeholder": "Search Gsm" + }, + "email": { + "label": "Email", + "placeholder": "Search Email" + }, + "submit": "Filter" }, "steps": { - "content": "Content", - "relations": "Relations" + "personal": "Personal", + "company": "Company", + "vehicle": "Vehicle" + }, + "actions": { + "add": "Add new courier", + "edit": "Edit courier" } }, "dashboard": { "title": "Dashboard", + "overview": { + "title": "Overview" + }, + "filter": { + "title": "Filters", + "date": { + "lastWeek": "Last Week", + "lastMonth": "Last Month", + "lastYear": "Last Year" + } + }, "deliveryMap": { "title": "Delivery Map" }, "orders": { "title": "Orders" }, + "revenue": { + "title": "Revenue" + }, "totalSales": { "title": "Total Sales" }, @@ -208,6 +297,9 @@ }, "trendingMenus": { "title": "Daily Trending Menus" + }, + "trendingProducts": { + "title": "Trending Products" } }, "enum": { @@ -221,12 +313,16 @@ }, "orders": { "orders": "Orders", + "order": "Order", "titles": { + "deliveryMap": "Delivery Map", + "deliveryDetails": "Delivery Details", + "products": "Products", "list": "Orders", - "show": "Show Order" + "show": "Order" }, "fields": { - "orderNumber": "Order Number", + "orderNumber": "Order #", "orderID": "Order ID", "status": "Status", "store": "Store", @@ -234,7 +330,12 @@ "products": "Products", "createdAt": "CreatedAt", "amount": "Amount", - "itemsAmount": "{{amount}} items" + "itemsAmount": "{{amount}} items", + "order": "Order", + "customer": "Customer", + "courier": "Courier", + "phone": "Phone", + "deliveryTime": "Delivery Time" }, "filter": { "title": "Filters", @@ -254,9 +355,17 @@ "label": "User", "placeholder": "Search Users" }, + "customer": { + "label": "Customer", + "placeholder": "Search Customers" + }, "createdAt": { "label": "Created At" }, + "orderNumber": { + "label": "Order Number", + "placeholder": "Search Order Number" + }, "submit": "Filter" }, "courier": { @@ -276,7 +385,6 @@ }, "products": { "products": "Products", - "noProducts": "No Products", "titles": { "list": "Products", "create": "Create Product", @@ -286,15 +394,55 @@ "id": "ID", "name": "Name", "description": "Description", - "isActive": "Active", + "isActive": { + "label": "Status", + "true": "Available", + "false": "Unavailable" + }, "price": "Price", "category": "Category", "images": { - "label": "Images", - "description": "Add product picture", + "label": "Image", + "description": "Upload Image", "validation": "must be 1080x1080 px" }, "createdAt": "CreatedAt" + }, + "filter": { + "title": "Filters", + "allCategories": { + "label": "All Categories", + "placeholder": "All Categories" + }, + "id": { + "label": "ID", + "placeholder": "Search ID" + }, + "name": { + "label": "Name", + "placeholder": "Search Name" + }, + "description": { + "label": "Description", + "placeholder": "Search Description" + }, + "category": { + "label": "Category", + "placeholder": "Search Category" + }, + "createdAt": { + "label": "Created At" + }, + "isActive": { + "label": "Status", + "placeholder": "Select Status", + "true": "Available", + "false": "Unavailable" + } + }, + "actions": { + "add": "Add new product", + "edit": "Edit product" } }, "reviews": { @@ -304,7 +452,7 @@ }, "fields": { "user": "User", - "orderId": "Order ID", + "orderId": "Order", "review": "Review", "rating": "Rating" } @@ -320,12 +468,49 @@ "fields": { "id": "Store ID", "title": "Title", - "isActive": "Active", + "isActive": { + "label": "Status", + "placeholder": "Select Status", + "true": "Open", + "false": "Closed" + }, "createdAt": "CreatedAt", "address": "Address", "email": "Email", "gsm": "Phone" }, + "filter": { + "id": { + "label": "ID", + "placeholder": "Search ID" + }, + "title": { + "label": "Title", + "placeholder": "Search Title" + }, + "email": { + "label": "Email", + "placeholder": "Search Email" + }, + "gsm": { + "label": "Gsm", + "placeholder": "Search Gsm" + }, + "search": { + "label": "Search", + "placeholder": "FirstName, lastName, gsm, etc." + }, + "createdAt": { + "label": "Created At" + }, + "isActive": { + "label": "Status", + "placeholder": "Select Status", + "true": "Open", + "false": "Closed" + }, + "submit": "Filter" + }, "buttons": { "editProducts": "Edit Products", "addProduct": "Add Product", @@ -336,14 +521,15 @@ "storeProducts": "Store Products", "tagFilterDescription": "Use tags to filter your search", "all": "All", - "productSearch": "Product Search" + "productSearch": "Product Search", + "addNewStore": "Add New Store" }, "users": { - "users": "Users", + "users": "Customers", "titles": { - "edit": "Edit User", - "list": "Users", - "show": "User Detail" + "edit": "Edit Customer", + "list": "Customers", + "show": "Customer Detail" }, "fields": { "id": "ID", @@ -353,21 +539,35 @@ }, "firstName": "First Name", "lastName": "Last Name", + "name": "Name", "gender": { "label": "Gender", "Male": "Male", "Female": "Female" }, - "gsm": "Gsm Number", - "createdAt": "CreatedAt", + "gsm": "Gsm No", + "createdAt": "Created At", + "addresses": "Addresses", "isActive": { - "label": "Active", - "true": "Yes", - "false": "No" + "label": "Status", + "true": "Active", + "false": "Idle" } }, "filter": { "title": "Filters", + "id": { + "label": "ID", + "placeholder": "Search ID" + }, + "name": { + "label": "Name", + "placeholder": "Search Name" + }, + "gsm": { + "label": "Gsm", + "placeholder": "Search Gsm" + }, "search": { "label": "Search", "placeholder": "FirstName, lastName, gsm, etc." @@ -383,7 +583,7 @@ }, "isActive": { "label": "Active", - "placeholder": "Active status", + "placeholder": "Select Status", "true": "Yes", "false": "No" }, @@ -396,11 +596,7 @@ }, "errors": { "required": { - "field": "'{{ field }}' is required", - "common": "This field is required", - "min": "This field must be at least {{ min }} characters", - "max": "This field must be at most {{ max }} characters", - "invalidMail": "This field must be a valid email address" + "field": "This field is required" } } } diff --git a/examples/finefoods-material-ui/src/App.tsx b/examples/finefoods-material-ui/src/App.tsx index 110b1ef2e823..9e45938c34d3 100644 --- a/examples/finefoods-material-ui/src/App.tsx +++ b/examples/finefoods-material-ui/src/App.tsx @@ -18,31 +18,24 @@ import routerProvider, { import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; import { useTranslation } from "react-i18next"; import AddShoppingCartOutlined from "@mui/icons-material/AddShoppingCartOutlined"; -import StarBorderOutlined from "@mui/icons-material/StarBorderOutlined"; import CategoryOutlined from "@mui/icons-material/CategoryOutlined"; import StoreOutlined from "@mui/icons-material/StoreOutlined"; import LocalPizzaOutlined from "@mui/icons-material/LocalPizzaOutlined"; import PeopleOutlineOutlined from "@mui/icons-material/PeopleOutlineOutlined"; +import MopedOutlined from "@mui/icons-material/MopedOutlined"; import Dashboard from "@mui/icons-material/Dashboard"; - +import Box from "@mui/material/Box"; import { authProvider } from "./authProvider"; import { DashboardPage } from "./pages/dashboard"; import { OrderList, OrderShow } from "./pages/orders"; -import { UserList, UserShow } from "./pages/users"; -import { ReviewsList } from "./pages/reviews"; -import { - CourierList, - CourierShow, - CourierCreate, - CourierEdit, -} from "./pages/couriers"; +import { CustomerShow, CustomerList } from "./pages/customers"; +import { CourierList, CourierCreate, CourierEdit } from "./pages/couriers"; import { AuthPage } from "./pages/auth"; import { StoreList, StoreEdit, StoreCreate } from "./pages/stores"; -import { ProductList } from "./pages/products"; +import { ProductEdit, ProductList, ProductCreate } from "./pages/products"; import { CategoryList } from "./pages/categories"; import { ColorModeContextProvider } from "./contexts"; -import { Header, Title, OffLayoutArea } from "./components"; -import { BikeWhiteIcon } from "./components/icons/bike-white"; +import { Header, Title } from "./components"; import { useAutoLoginForDemo } from "./hooks"; const API_URL = "https://api.finefoods.refine.dev"; @@ -65,7 +58,6 @@ const App: React.FC = () => { return ( - @@ -79,6 +71,7 @@ const App: React.FC = () => { options={{ syncWithLocation: true, warnWhenUnsavedChanges: true, + breadcrumb: false, }} notificationProvider={useNotificationProvider} resources={[ @@ -100,8 +93,8 @@ const App: React.FC = () => { }, { name: "users", - list: "/users", - show: "/users/show/:id", + list: "/customers", + show: "/customers/show/:id", meta: { icon: , }, @@ -109,10 +102,19 @@ const App: React.FC = () => { { name: "products", list: "/products", + create: "/products/create", + edit: "/products/edit/:id", meta: { icon: , }, }, + { + name: "categories", + list: "/categories", + meta: { + icon: , + }, + }, { name: "stores", list: "/stores", @@ -122,28 +124,13 @@ const App: React.FC = () => { icon: , }, }, - { - name: "categories", - list: "/categories", - meta: { - icon: , - }, - }, { name: "couriers", list: "/couriers", create: "/couriers/create", edit: "/couriers/edit/:id", - show: "/couriers/show/:id", - meta: { - icon: , - }, - }, - { - name: "reviews", - list: "/reviews", meta: { - icon: , + icon: , }, }, ]} @@ -155,12 +142,16 @@ const App: React.FC = () => { key="authenticated-routes" fallback={} > - - + + + + } @@ -171,13 +162,30 @@ const App: React.FC = () => { } /> } /> - - - } /> - } /> + + + + } + > + + } /> - } /> + + + + } + > + + } /> + } /> + } /> @@ -188,13 +196,19 @@ const App: React.FC = () => { } /> - } /> - } /> + + + + } + > + } /> + + } /> - } /> - - } /> { - + diff --git a/examples/finefoods-material-ui/src/authProvider.ts b/examples/finefoods-material-ui/src/authProvider.ts index 5d2d40138f23..d22266795f23 100644 --- a/examples/finefoods-material-ui/src/authProvider.ts +++ b/examples/finefoods-material-ui/src/authProvider.ts @@ -28,7 +28,8 @@ export const authProvider: AuthProvider = { }; } }, - updatePassword: async () => { + updatePassword: async (params) => { + console.log(params); return { success: true, }; diff --git a/examples/finefoods-material-ui/src/components/card/index.tsx b/examples/finefoods-material-ui/src/components/card/index.tsx new file mode 100644 index 000000000000..2961c09e894a --- /dev/null +++ b/examples/finefoods-material-ui/src/components/card/index.tsx @@ -0,0 +1,65 @@ +import { PropsWithChildren } from "react"; +import CardBase, { CardProps } from "@mui/material/Card"; +import CardHeader, { CardHeaderProps } from "@mui/material/CardHeader"; +import CardContent, { CardContentProps } from "@mui/material/CardContent"; +import Divider from "@mui/material/Divider"; +import Typography from "@mui/material/Typography"; + +type Props = { + title?: React.ReactNode; + icon?: React.ReactNode; + cardHeaderProps?: CardHeaderProps; + cardContentProps?: CardContentProps; +} & CardProps; + +export const Card = ({ + icon, + title, + cardHeaderProps, + cardContentProps, + children, + ...rest +}: PropsWithChildren) => { + return ( + + + {title} + + } + avatar={icon} + sx={{ + height: "56px", + ".MuiCardHeader-avatar": { + color: "primary.main", + marginRight: "8px", + }, + ".MuiCardHeader-action": { + margin: 0, + }, + }} + {...cardHeaderProps} + /> + + + {children} + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/category/index.ts b/examples/finefoods-material-ui/src/components/category/index.ts new file mode 100644 index 000000000000..ed1ec7efa79a --- /dev/null +++ b/examples/finefoods-material-ui/src/components/category/index.ts @@ -0,0 +1 @@ +export * from "./status"; diff --git a/examples/finefoods-material-ui/src/components/category/status/index.tsx b/examples/finefoods-material-ui/src/components/category/status/index.tsx new file mode 100644 index 000000000000..a8d8024b9944 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/category/status/index.tsx @@ -0,0 +1,26 @@ +import Chip, { ChipProps } from "@mui/material/Chip"; +import { useTranslate } from "@refinedev/core"; +import { IProduct } from "../../../interfaces"; +import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; +import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; + +type Props = { + value: IProduct["isActive"]; + size?: ChipProps["size"]; +}; + +export const CategoryStatus = (props: Props) => { + const t = useTranslate(); + + return ( + : + } + variant="outlined" + size={props.size} + /> + ); +}; diff --git a/examples/finefoods-material-ui/src/components/courier/image-upload/index.tsx b/examples/finefoods-material-ui/src/components/courier/image-upload/index.tsx new file mode 100644 index 000000000000..107813b4da80 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/courier/image-upload/index.tsx @@ -0,0 +1,125 @@ +import { SxProps, styled } from "@mui/material/styles"; +import PhotoOutlinedIcon from "@mui/icons-material/PhotoOutlined"; +import CloudUploadOutlinedIcon from "@mui/icons-material/CloudUploadOutlined"; +import Box from "@mui/material/Box"; + +type Props = { + previewURL?: string; + sx?: SxProps; + inputProps?: React.InputHTMLAttributes; + showOverlay?: boolean; +}; + +export const CourierImageUpload = ({ + inputProps, + previewURL, + sx, + showOverlay = true, +}: Props) => { + return ( + + {previewURL ? ( + + ) : ( + + + + )} + {showOverlay && ( + + + + )} + + + + + ); +}; + +const Overlay = styled("div")({ + display: "flex", + alignItems: "center", + justifyContent: "center", + position: "absolute", + bottom: 0, + width: "100%", + height: "50%", + backgroundColor: "rgba(0, 0, 0, 0.45)", + border: 0, + borderBottomLeftRadius: "100%", + borderBottomRightRadius: "100%", + color: "white", + "&:hover": { + backgroundColor: "rgba(0, 0, 0, 0.2)", + transition: "background-color 0.3s ease", + }, +}); + +const VisuallyHiddenInput = styled("input")({ + clip: "rect(0 0 0 0)", + clipPath: "inset(50%)", + height: 1, + overflow: "hidden", + position: "absolute", + bottom: 0, + left: 0, + whiteSpace: "nowrap", + width: 1, +}); diff --git a/examples/finefoods-material-ui/src/components/courier/index.ts b/examples/finefoods-material-ui/src/components/courier/index.ts new file mode 100644 index 000000000000..fd1e7441e24a --- /dev/null +++ b/examples/finefoods-material-ui/src/components/courier/index.ts @@ -0,0 +1,4 @@ +export * from "./rating"; +export * from "./status"; +export * from "./image-upload"; +export * from "./table-reviews"; diff --git a/examples/finefoods-material-ui/src/components/courier/rating/index.tsx b/examples/finefoods-material-ui/src/components/courier/rating/index.tsx new file mode 100644 index 000000000000..e9bff1e838ea --- /dev/null +++ b/examples/finefoods-material-ui/src/components/courier/rating/index.tsx @@ -0,0 +1,37 @@ +import Rating from "@mui/material/Rating"; +import { ICourier, IReview } from "../../../interfaces"; +import { useList } from "@refinedev/core"; + +type Props = { + courier?: ICourier; +}; + +export const CourierRating = (props: Props) => { + const { data } = useList({ + resource: "reviews", + filters: [ + { + field: "order.courier.id", + operator: "eq", + value: props.courier?.id, + }, + ], + pagination: { + mode: "off", + }, + queryOptions: { + enabled: !!props.courier?.id, + }, + }); + + const review = data?.data || []; + const totalStarCount = review?.reduce( + (acc, curr) => acc + (curr?.star || 0), + 0, + ); + const avgStar = totalStarCount / (review?.length || 1); + + return ( + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/courier/status/index.tsx b/examples/finefoods-material-ui/src/components/courier/status/index.tsx new file mode 100644 index 000000000000..d708e21656a7 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/courier/status/index.tsx @@ -0,0 +1,44 @@ +import Chip, { ChipProps } from "@mui/material/Chip"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import BlockOutlinedIcon from "@mui/icons-material/BlockOutlined"; +import MopedOutlined from "@mui/icons-material/MopedOutlined"; +import { ICourierStatus } from "../../../interfaces"; + +type Variant = { + [key in ICourierStatus["text"]]: { + icon: ChipProps["icon"]; + tagColor: ChipProps["color"]; + }; +}; + +type Props = { + value: ICourierStatus; +}; + +export const CourierStatus = (props: Props) => { + const variant: Variant = { + Available: { + tagColor: "success", + icon: , + }, + Offline: { + tagColor: "default", + icon: , + }, + "On delivery": { + tagColor: "info", + icon: , + }, + }; + + const text = props?.value?.text || "Offline"; + + return ( + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/courier/table-reviews/index.tsx b/examples/finefoods-material-ui/src/components/courier/table-reviews/index.tsx new file mode 100644 index 000000000000..bbfa8dcd7dc8 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/courier/table-reviews/index.tsx @@ -0,0 +1,94 @@ +import { useMemo } from "react"; +import { useTranslate } from "@refinedev/core"; +import { useDataGrid } from "@refinedev/mui"; +import Paper from "@mui/material/Paper"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import Rating from "@mui/material/Rating"; +import Chip from "@mui/material/Chip"; +import { ICourier, IReview } from "../../../interfaces"; + +interface Props { + courier?: ICourier; +} + +export const CourierTableReviews = (props: Props) => { + const t = useTranslate(); + + const { dataGridProps } = useDataGrid({ + resource: "reviews", + pagination: { + pageSize: 10, + }, + filters: { + permanent: [ + { + field: "courier.id", + operator: "eq", + value: props.courier?.id, + }, + ], + }, + queryOptions: { + enabled: !!props.courier?.id, + }, + syncWithLocation: false, + }); + + const columns = useMemo[]>( + () => [ + { + field: "comment", + headerName: t("reviews.reviews"), + flex: 1, + sortable: false, + filterable: false, + }, + { + field: "star", + headerName: t("reviews.fields.rating"), + width: 172, + filterable: false, + renderCell: function render({ row }: { row: IReview }) { + return ( + + ); + }, + }, + { + field: "orderNumber", + headerName: t("orders.fields.orderNumber"), + filterable: false, + sortable: false, + renderCell: function render({ row }: { row: IReview }) { + return ( + + ); + }, + }, + ], + [t], + ); + + return ( + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/courierInfoBox/index.tsx b/examples/finefoods-material-ui/src/components/courierInfoBox/index.tsx deleted file mode 100644 index 679d1cd63391..000000000000 --- a/examples/finefoods-material-ui/src/components/courierInfoBox/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import Box from "@mui/material/Box"; -import Avatar from "@mui/material/Avatar"; -import Typography from "@mui/material/Typography"; - -type CourierInfoBoxProps = { - text: string; - icon: React.ReactNode; - value?: string; -}; - -export const CourierInfoBox: React.FC = ({ - text, - icon, - value, -}) => { - return ( - - - {icon} - - - - {text} - - {value} - - - ); -}; diff --git a/examples/finefoods-material-ui/src/components/customer/index.ts b/examples/finefoods-material-ui/src/components/customer/index.ts new file mode 100644 index 000000000000..ed1ec7efa79a --- /dev/null +++ b/examples/finefoods-material-ui/src/components/customer/index.ts @@ -0,0 +1 @@ +export * from "./status"; diff --git a/examples/finefoods-material-ui/src/components/customer/status/index.tsx b/examples/finefoods-material-ui/src/components/customer/status/index.tsx new file mode 100644 index 000000000000..d58759cf4366 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/customer/status/index.tsx @@ -0,0 +1,30 @@ +import Chip, { ChipProps } from "@mui/material/Chip"; +import { useTranslate } from "@refinedev/core"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import PauseCircleOutlineOutlinedIcon from "@mui/icons-material/PauseCircleOutlineOutlined"; +import { IUser } from "../../../interfaces"; + +type Props = { + value: IUser["isActive"]; +} & ChipProps; + +export const CustomerStatus = ({ value, ...rest }: Props) => { + const t = useTranslate(); + + const color: ChipProps["color"] = value ? "success" : "default"; + const icon: ChipProps["icon"] = value ? ( + + ) : ( + + ); + + return ( + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/dashboard/chartTooltip/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/chartTooltip/index.tsx index 5551d41d50c1..3460b49c7ada 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/chartTooltip/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/chartTooltip/index.tsx @@ -1,18 +1,30 @@ +import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; import { TooltipProps } from "recharts"; type ChartTooltipProps = TooltipProps & { - suffix?: string; + labelFormatter?: (label: string | number) => string; + valueFormatter?: (value: string | number) => string; }; export const ChartTooltip: React.FC = ({ active, payload, - suffix = "", + valueFormatter, + labelFormatter, }) => { if (active && payload?.length) { + const value = + valueFormatter?.(payload[0]?.value || "") || payload[0]?.value; + + const label = + labelFormatter?.(payload[0]?.payload?.date || "") || + payload[0]?.payload?.date; return ( = ({ borderRadius: "4px", }} > - {`${payload[0]?.value} ${suffix}`} + {value} + {label} ); } diff --git a/examples/finefoods-material-ui/src/components/dashboard/dailyOrders/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/dailyOrders/index.tsx index 426c33c3deca..736212a2cc09 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/dailyOrders/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/dailyOrders/index.tsx @@ -1,76 +1,50 @@ -import { useApiUrl, useCustom, useTranslate } from "@refinedev/core"; -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { BarChart, Bar, Tooltip, ResponsiveContainer } from "recharts"; -import ArrowDropDown from "@mui/icons-material/ArrowDropDown"; -import ArrowDropUp from "@mui/icons-material/ArrowDropUp"; - +import { + BarChart, + Bar, + Tooltip, + ResponsiveContainer, + XAxis, + YAxis, +} from "recharts"; import { ChartTooltip } from "../chartTooltip"; -import { ISalesChart } from "../../../interfaces"; - -export const DailyOrders: React.FC = () => { - const t = useTranslate(); - - const API_URL = useApiUrl(); - const url = `${API_URL}/dailyOrders`; +import dayjs from "dayjs"; - const { data } = useCustom<{ - data: ISalesChart[]; - total: number; - trend: number; - }>({ - url, - method: "get", - }); +type Props = { + data: any[]; +}; +export const DailyOrders = (props: Props) => { return ( - - - - {t("dashboard.dailyOrders.title")} - + + + { + if (props.data.length > 7) { + return dayjs(value).format("MM/DD"); + } - - - {data?.data.total ?? 0} - - {(data?.data?.trend ?? 0) > 0 ? ( - - ) : ( - - )} - - - - - - - } + return dayjs(value).format("ddd"); + }} + /> + + + dayjs(label).format("MMM D, YYYY")} /> - - - - + } + /> + + ); }; diff --git a/examples/finefoods-material-ui/src/components/dashboard/dailyRevenue/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/dailyRevenue/index.tsx index e9a519ec8de7..09bec8c90d23 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/dailyRevenue/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/dailyRevenue/index.tsx @@ -1,86 +1,63 @@ -import { useApiUrl, useCustom, useTranslate } from "@refinedev/core"; -import { NumberField } from "@refinedev/mui"; -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import dayjs from "dayjs"; -import { LineChart, Line, Tooltip, ResponsiveContainer } from "recharts"; -import ArrowDropDown from "@mui/icons-material/ArrowDropDown"; -import ArrowDropUp from "@mui/icons-material/ArrowDropUp"; +import { + LineChart, + Line, + Tooltip, + ResponsiveContainer, + XAxis, + YAxis, +} from "recharts"; import { ChartTooltip } from "../chartTooltip"; -import { ISalesChart } from "../../../interfaces"; - -export const DailyRevenue: React.FC = () => { - const t = useTranslate(); - - const API_URL = useApiUrl(); - const url = `${API_URL}/dailyRevenue`; - const query = { - start: dayjs().subtract(7, "days").startOf("day"), - end: dayjs().startOf("day"), - }; +import dayjs from "dayjs"; - const { data } = useCustom<{ - data: ISalesChart[]; - total: number; - trend: number; - }>({ - url, - method: "get", - config: { - query, - }, - }); +type Props = { + data: any[]; +}; +export const DailyRevenue = (props: Props) => { return ( - - - - {t("dashboard.dailyRevenue.title")} - - - - {(data?.data?.trend ?? 0) > 0 ? ( - - ) : ( - - )} - - - - - - + + { + if (props.data.length > 7) { + return dayjs(value).format("MM/DD"); + } + + return dayjs(value).format("ddd"); + }} + /> + { + return `$${Number(value) / 1000}k`; + }} + /> + + + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(Number(value)) + } + labelFormatter={(label) => dayjs(label).format("MMM D, YYYY")} /> - } /> - - - - + } + /> + + ); }; diff --git a/examples/finefoods-material-ui/src/components/dashboard/deliveryMap/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/deliveryMap/index.tsx index 96eb1ef053fe..09171f388940 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/deliveryMap/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/deliveryMap/index.tsx @@ -1,5 +1,4 @@ import { useList, useNavigation } from "@refinedev/core"; -import Box from "@mui/material/Box"; import { Map, MapMarker } from "../../../components"; import { IOrder } from "../../../interfaces"; @@ -32,39 +31,37 @@ export const DeliveryMap: React.FC = () => { const { show } = useNavigation(); return ( - - - {orderData?.data.map((order) => { - return ( - show("orders", order.id)} - icon={{ - url: "/images/marker-courier.svg", - }} - position={{ - lat: Number(order.adress.coordinate[0]), - lng: Number(order.adress.coordinate[1]), - }} - /> - ); - })} - {orderData?.data.map((order) => { - return ( - show("orders", order.id)} - icon={{ - url: "/images/marker-location.svg", - }} - position={{ - lat: Number(order.store.address.coordinate[0]), - lng: Number(order.store.address.coordinate[1]), - }} - /> - ); - })} - - + + {orderData?.data.map((order) => { + return ( + show("orders", order.id)} + icon={{ + url: "/images/marker-courier.svg", + }} + position={{ + lat: Number(order.adress.coordinate[0]), + lng: Number(order.adress.coordinate[1]), + }} + /> + ); + })} + {orderData?.data.map((order) => { + return ( + show("orders", order.id)} + icon={{ + url: "/images/marker-location.svg", + }} + position={{ + lat: Number(order.store.address.coordinate[0]), + lng: Number(order.store.address.coordinate[1]), + }} + /> + ); + })} + ); }; diff --git a/examples/finefoods-material-ui/src/components/dashboard/newCustomers/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/newCustomers/index.tsx index aa81f573ff07..10c4ee6ceca0 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/newCustomers/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/newCustomers/index.tsx @@ -1,88 +1,50 @@ -import { useApiUrl, useCustom, useTranslate } from "@refinedev/core"; -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { BarChart, Bar, Tooltip, ResponsiveContainer } from "recharts"; -import ArrowDropDown from "@mui/icons-material/ArrowDropDown"; -import ArrowDropUp from "@mui/icons-material/ArrowDropUp"; - +import { + BarChart, + Bar, + Tooltip, + ResponsiveContainer, + XAxis, + YAxis, +} from "recharts"; import { ChartTooltip } from "../chartTooltip"; -import { ISalesChart } from "../../../interfaces"; - -export const NewCustomers: React.FC = () => { - const t = useTranslate(); - - const API_URL = useApiUrl(); - const url = `${API_URL}/newCustomers`; +import dayjs from "dayjs"; - const { data } = useCustom<{ - data: ISalesChart[]; - total: number; - trend: number; - }>({ - url, - method: "get", - }); +type Props = { + data: any[]; +}; +export const NewCustomers = (props: Props) => { return ( - - - - {t("dashboard.newCustomers.title")} - + + + { + if (props.data.length > 7) { + return dayjs(value).format("MM/DD"); + } - - - {data?.data.total ?? 0} - - - - {data?.data.trend ?? 0}% - - {(data?.data?.trend ?? 0) > 0 ? ( - - ) : ( - - )} - - - - - - - - } + return dayjs(value).format("ddd"); + }} + /> + + + dayjs(label).format("MMM D, YYYY")} /> - - - - + } + /> + + ); }; diff --git a/examples/finefoods-material-ui/src/components/dashboard/orderTimeline/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/orderTimeline/index.tsx index 43bcd07a0407..3d0ce3173382 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/orderTimeline/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/orderTimeline/index.tsx @@ -1,25 +1,18 @@ import { useTranslate, useNavigation, useTable } from "@refinedev/core"; -import Timeline from "@mui/lab/Timeline"; -import TimelineConnector from "@mui/lab/TimelineConnector"; -import TimelineContent from "@mui/lab/TimelineContent"; -import TimelineDot from "@mui/lab/TimelineDot"; -import TimelineItem from "@mui/lab/TimelineItem"; -import TimelineOppositeContent from "@mui/lab/TimelineOppositeContent"; -import TimelineSeparator from "@mui/lab/TimelineSeparator"; import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import Tooltip from "@mui/material/Tooltip"; -import Typography from "@mui/material/Typography"; import Pagination from "@mui/material/Pagination"; import dayjs from "dayjs"; - import { IOrder } from "../../../interfaces"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemAvatar from "@mui/material/ListItemAvatar"; +import ListItemText from "@mui/material/ListItemText"; +import { OrderStatus } from "../../order"; export const OrderTimeline: React.FC = () => { const theme = useTheme(); - const t = useTranslate(); const { show } = useNavigation(); const { tableQueryResult, current, setCurrent, pageCount } = useTable( @@ -31,113 +24,65 @@ export const OrderTimeline: React.FC = () => { order: "desc", }, ], - initialPageSize: 5, + initialPageSize: 7, syncWithLocation: false, }, ); const { data } = tableQueryResult; - const orderStatusColor = ( - id: string, - ): { color: string; text: string; dotColor: string } => { - switch (id) { - case "1": - return { - color: theme.timeLine.color.pending, - text: "pending", - dotColor: theme.timeLine.dotColor.pending, - }; - case "2": - return { - color: theme.timeLine.color.ready, - text: "ready", - dotColor: theme.timeLine.dotColor.ready, - }; - case "3": - return { - color: theme.timeLine.color.onTheWay, - text: "on the way", - dotColor: theme.timeLine.dotColor.onTheWay, - }; - case "4": - return { - color: theme.timeLine.color.delivered, - text: "delivered", - dotColor: theme.timeLine.dotColor.delivered, - }; - default: - return { - color: theme.timeLine.color.cancelled, - text: "cancelled", - dotColor: theme.timeLine.dotColor.cancelled, - }; - } - }; - return ( - <> - - {data?.data.map(({ createdAt, orderNumber, status, id }) => { - const text = orderStatusColor(status.id.toString())?.text; - const color = orderStatusColor(status.id.toString())?.color; - const dotColor = orderStatusColor(status.id.toString())?.dotColor; - + + + {data?.data?.map((order, i) => { + const isLast = i === data.data.length - 1; return ( - - - - - - - - - - - {dayjs(createdAt).fromNow()} - - - - {t(`dashboard.timeline.orderStatuses.${text}`)} - - - - - + show("orders", order.id)} + sx={{ + cursor: "pointer", + "&:hover": { + backgroundColor: theme.palette.action.hover, + }, + }} + > + + + + + ); })} - - setCurrent(page)} - siblingCount={0} - boundaryCount={0} - size="small" - color="primary" - /> - - - + + + setCurrent(page)} + siblingCount={1} + boundaryCount={1} + size="small" + color="primary" + /> + + ); }; diff --git a/examples/finefoods-material-ui/src/components/dashboard/recentOrders/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/recentOrders/index.tsx index 902f48d8f514..012eae2addd1 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/recentOrders/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/recentOrders/index.tsx @@ -1,21 +1,30 @@ -import React from "react"; -import { useNavigation, useTranslate, useUpdate } from "@refinedev/core"; +import React, { useEffect } from "react"; +import { + useNavigation, + useTranslate, + useUpdate, + useUpdatePassword, +} from "@refinedev/core"; import { NumberField, useDataGrid } from "@refinedev/mui"; import CheckOutlined from "@mui/icons-material/CheckOutlined"; import CloseOutlined from "@mui/icons-material/CloseOutlined"; -import Avatar from "@mui/material/Avatar"; -import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid"; - -import { OrderStatus } from "../../../components/orderStatus"; import { IOrder } from "../../../interfaces"; +import { getUniqueListWithCount } from "../../../utils"; export const RecentOrders: React.FC = () => { const t = useTranslate(); const { show } = useNavigation(); const { mutate } = useUpdate(); + const { mutate: updatePassword } = useUpdatePassword(); + useEffect(() => { + updatePassword({ + redirectPath: "/custom-url", + query: "?foo=bar", + }); + }, []); const { dataGridProps } = useDataGrid({ resource: "orders", @@ -25,7 +34,7 @@ export const RecentOrders: React.FC = () => { order: "desc", }, ], - initialPageSize: 4, + initialPageSize: 10, permanentFilter: [ { field: "status.text", @@ -39,88 +48,68 @@ export const RecentOrders: React.FC = () => { const columns = React.useMemo[]>( () => [ { - field: "avatar", + field: "orderNumber", renderCell: function render({ row }) { - return ( - - ); + return #{row.orderNumber}; }, - align: "center", - flex: 1, - minWidth: 100, + width: 88, }, { - field: "summary", + field: "user", + width: 220, renderCell: function render({ row }) { return ( - - - {row.products[0]?.name} - + + {row.user.fullName} - {row.products[0]?.description} + {row.user.addresses[0].text} - ); }, - flex: 2, - minWidth: 200, }, { - field: "summary2", + field: "products", + flex: 1, renderCell: function render({ row }) { + const products = getUniqueListWithCount({ + list: row.products, + field: "id", + }); return ( - - {`${row.courier.name} ${row.courier.surname}`} - {row.adress.text} + + {products.map((product) => ( + + {product.name} + + x{product.count} + + + ))} ); }, - flex: 1, - minWidth: 100, }, { field: "amount", + align: "right", + width: 80, renderCell: function render({ row }) { return ( { style: "currency", notation: "standard", }} - sx={{ fontWeight: 800 }} - value={row.amount / 100} + value={row.amount} /> ); }, - align: "center", - flex: 1, - width: 80, - }, - { - field: "status", - align: "center", - renderCell: function render({ row }) { - return ; - }, }, { field: "actions", @@ -197,14 +175,24 @@ export const RecentOrders: React.FC = () => { return ( show("orders", row.id)} columns={columns} - autoHeight columnHeaderHeight={0} - rowHeight={200} - pageSizeOptions={[4, 10, 25, 50, 100]} + pageSizeOptions={[10, 25, 50, 100]} sx={{ - paddingX: { xs: 3 }, + height: "100%", border: "none", + "& .MuiDataGrid-row": { + cursor: "pointer", + maxHeight: "max-content !important", + minHeight: "max-content !important", + }, + "& .MuiDataGrid-cell": { + maxHeight: "max-content !important", + minHeight: "max-content !important", + padding: "16px", + alignItems: "flex-start", + }, }} /> ); diff --git a/examples/finefoods-material-ui/src/components/dashboard/trendingMenu/index.tsx b/examples/finefoods-material-ui/src/components/dashboard/trendingMenu/index.tsx index 9cd3a4ad20a4..dd0638c513bf 100644 --- a/examples/finefoods-material-ui/src/components/dashboard/trendingMenu/index.tsx +++ b/examples/finefoods-material-ui/src/components/dashboard/trendingMenu/index.tsx @@ -6,21 +6,29 @@ import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; -import { IOrder } from "../../../interfaces"; +import { ITrendingProducts } from "../../../interfaces"; +import { Rank1Icon } from "../../icons/rank-1"; +import { Rank2Icon } from "../../icons/rank-2"; +import { Rank3Icon } from "../../icons/rank-3"; +import { Rank4Icon } from "../../icons/rank-4"; +import { Rank5Icon } from "../../icons/rank-5"; +import { ReactNode } from "react"; export const TrendingMenu: React.FC = () => { - const { data } = useList({ - resource: "orders", + const { data } = useList({ + resource: "trendingProducts", config: { pagination: { pageSize: 5 }, }, }); + const trending = data?.data || []; + return ( - - {data?.data.map((order, index) => ( + + {trending.map((item, index) => ( { - #{index + 1} + {RankIcons[index + 1]} - - - {order.products[0]?.name} - + + {item.product.name} + + Ordered + + {" "} + {item.orderCount}{" "} + + times + ))} ); }; + +const RankIcons: Record = { + 1: , + 2: , + 3: , + 4: , + 5: , +}; diff --git a/examples/finefoods-material-ui/src/components/drawer/drawer/index.tsx b/examples/finefoods-material-ui/src/components/drawer/drawer/index.tsx new file mode 100644 index 000000000000..ff72edd63c70 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/drawer/drawer/index.tsx @@ -0,0 +1,24 @@ +import BaseDrawer, { DrawerProps } from "@mui/material/Drawer"; +import { PropsWithChildren } from "react"; +import gray from "@mui/material/colors/grey"; +import { useColorModeContext } from "../../../contexts"; + +type Props = {} & DrawerProps; + +export const Drawer = ({ children, ...props }: PropsWithChildren) => { + const { mode } = useColorModeContext(); + + return ( + + {children} + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/drawer/header/index.tsx b/examples/finefoods-material-ui/src/components/drawer/header/index.tsx new file mode 100644 index 000000000000..d5ed6a0f6a4e --- /dev/null +++ b/examples/finefoods-material-ui/src/components/drawer/header/index.tsx @@ -0,0 +1,36 @@ +import Close from "@mui/icons-material/Close"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import Divider from "@mui/material/Divider"; +import IconButton from "@mui/material/IconButton"; + +type Props = { + onCloseClick: () => void; + title?: string; +}; + +export const DrawerHeader = ({ title, onCloseClick }: Props) => { + return ( + <> + + {title && {title}} + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/drawer/index.ts b/examples/finefoods-material-ui/src/components/drawer/index.ts new file mode 100644 index 000000000000..8a65fe4e7b11 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/drawer/index.ts @@ -0,0 +1,2 @@ +export * from "./header"; +export * from "./drawer"; diff --git a/examples/finefoods-material-ui/src/components/icons/basic-marker.tsx b/examples/finefoods-material-ui/src/components/icons/basic-marker.tsx new file mode 100644 index 000000000000..897f47ebb208 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/basic-marker.tsx @@ -0,0 +1,18 @@ +import * as React from "react"; +import { SVGProps } from "react"; + +export const BasicMarker = (props: SVGProps) => ( + + + +); diff --git a/examples/finefoods-material-ui/src/components/icons/bike-white.tsx b/examples/finefoods-material-ui/src/components/icons/bike-white.tsx index cb1586143a2a..c0f4b621551f 100644 --- a/examples/finefoods-material-ui/src/components/icons/bike-white.tsx +++ b/examples/finefoods-material-ui/src/components/icons/bike-white.tsx @@ -4,9 +4,9 @@ import type { SvgIconProps } from "@mui/material/SvgIcon"; export const BikeWhiteIcon = (props: SvgIconProps) => { return ( { diff --git a/examples/finefoods-material-ui/src/components/icons/bike.tsx b/examples/finefoods-material-ui/src/components/icons/bike.tsx new file mode 100644 index 000000000000..2e87beb50d15 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/bike.tsx @@ -0,0 +1,17 @@ +export const BikeIcon: React.FC> = (props) => ( + + + +); diff --git a/examples/finefoods-material-ui/src/components/icons/courier.tsx b/examples/finefoods-material-ui/src/components/icons/courier.tsx index 471a7f083cc6..5f013164d243 100644 --- a/examples/finefoods-material-ui/src/components/icons/courier.tsx +++ b/examples/finefoods-material-ui/src/components/icons/courier.tsx @@ -1,17 +1,21 @@ -import SvgIcon from "@mui/material/SvgIcon"; -import type { SvgIconProps } from "@mui/material/SvgIcon"; - -export const CourierIcon = (props: SvgIconProps) => { - return ( - - - - - ); -}; +export const CourierIcon: React.FC> = (props) => ( + + + + +); diff --git a/examples/finefoods-material-ui/src/components/icons/finefoods-logo.tsx b/examples/finefoods-material-ui/src/components/icons/finefoods-logo.tsx new file mode 100644 index 000000000000..604c4e88d710 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/finefoods-logo.tsx @@ -0,0 +1,72 @@ +import React from "react"; + +export const FinefoodsLogoIcon: React.FC> = ( + props, +) => ( + + + + + + + + + +); + +export const FinefoodsLogoText: React.FC> = ( + props, +) => { + return ( + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/icons/index.ts b/examples/finefoods-material-ui/src/components/icons/index.ts index 40baf1d29d91..7f38ebcd1f03 100644 --- a/examples/finefoods-material-ui/src/components/icons/index.ts +++ b/examples/finefoods-material-ui/src/components/icons/index.ts @@ -2,3 +2,4 @@ export * from "./courier"; export * from "./location"; export * from "./fine-foods"; export * from "./bike-white"; +export * from "./trend-icon"; diff --git a/examples/finefoods-material-ui/src/components/icons/rank-1.tsx b/examples/finefoods-material-ui/src/components/icons/rank-1.tsx new file mode 100644 index 000000000000..bef090cb703e --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/rank-1.tsx @@ -0,0 +1,94 @@ +import { SVGProps } from "react"; + +export const Rank1Icon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/icons/rank-2.tsx b/examples/finefoods-material-ui/src/components/icons/rank-2.tsx new file mode 100644 index 000000000000..3869ae967b03 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/rank-2.tsx @@ -0,0 +1,94 @@ +import { SVGProps } from "react"; + +export const Rank2Icon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/icons/rank-3.tsx b/examples/finefoods-material-ui/src/components/icons/rank-3.tsx new file mode 100644 index 000000000000..bef5f8586a62 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/rank-3.tsx @@ -0,0 +1,94 @@ +import { SVGProps } from "react"; + +export const Rank3Icon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/icons/rank-4.tsx b/examples/finefoods-material-ui/src/components/icons/rank-4.tsx new file mode 100644 index 000000000000..d0b40f5f2ece --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/rank-4.tsx @@ -0,0 +1,76 @@ +import { SVGProps } from "react"; + +export const Rank4Icon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/icons/rank-5.tsx b/examples/finefoods-material-ui/src/components/icons/rank-5.tsx new file mode 100644 index 000000000000..6ad8b25e4df0 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/rank-5.tsx @@ -0,0 +1,76 @@ +import { SVGProps } from "react"; + +export const Rank5Icon = (props: SVGProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/icons/trend-icon.tsx b/examples/finefoods-material-ui/src/components/icons/trend-icon.tsx new file mode 100644 index 000000000000..e677d61443c3 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/icons/trend-icon.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from "react"; +import ArrowDropUp from "@mui/icons-material/ArrowDropUp"; +import ArrowDropDown from "@mui/icons-material/ArrowDropDown"; +import RemoveIcon from "@mui/icons-material/Remove"; +import Box from "@mui/material/Box"; + +type Props = { + text?: ReactNode; + trend?: number; +}; + +export const TrendIcon = ({ text, trend }: Props) => { + const Icon = () => + trend ? ( + trend > 0 ? ( + + ) : ( + + ) + ) : ( + + ); + + return ( + + {text} + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/index.ts b/examples/finefoods-material-ui/src/components/index.ts index e7d5adc21416..fe3367872a1f 100644 --- a/examples/finefoods-material-ui/src/components/index.ts +++ b/examples/finefoods-material-ui/src/components/index.ts @@ -1,8 +1,14 @@ -export * from "./orderStatus"; +export * from "./order/status"; export * from "./customTooltip"; -export * from "./courierInfoBox"; export * from "./map"; export * from "./product"; export * from "./header"; export * from "./title"; -export * from "./offLayoutArea"; +export * from "./card"; +export * from "./customer"; +export * from "./refine-list-view"; +export * from "./drawer"; +export * from "./order"; +export * from "./category"; +export * from "./store"; +export * from "./courier"; diff --git a/examples/finefoods-material-ui/src/components/map/advanced-marker.tsx b/examples/finefoods-material-ui/src/components/map/advanced-marker.tsx new file mode 100644 index 000000000000..51260b1d81df --- /dev/null +++ b/examples/finefoods-material-ui/src/components/map/advanced-marker.tsx @@ -0,0 +1,61 @@ +import { useState, useEffect, memo, useRef, PropsWithChildren } from "react"; +import { Root, createRoot } from "react-dom/client"; + +type AdvancedMarkerProps = { + onClick?: Function; + map?: google.maps.Map; +} & google.maps.marker.AdvancedMarkerElementOptions; + +const AdvancedMarker: React.FC> = ({ + onClick, + map, + children, + zIndex, + ...options +}) => { + const rootRef = useRef(null); + const [marker, setMarker] = + useState(); + + useEffect(() => { + if (!map) return; + + if (!marker) { + const container = document.createElement("div"); + rootRef.current = createRoot(container); + + setMarker( + new google.maps.marker.AdvancedMarkerElement({ + ...options, + gmpClickable: !!onClick, + content: container, + map, + }), + ); + } + }, [marker, map]); + + useEffect(() => { + if (marker) { + marker.zIndex = zIndex; + } + }, [marker, zIndex]); + + useEffect(() => { + if (!marker) return; + + rootRef?.current?.render(children); + if (onClick) { + google.maps.event.addListener(marker, "gmp-click", onClick); + } + return () => { + if (marker) { + google.maps.event.clearListeners(marker, "gmp-click"); + } + }; + }, [marker, children, onClick]); + + return null; +}; + +export default memo(AdvancedMarker); diff --git a/examples/finefoods-material-ui/src/components/map/index.tsx b/examples/finefoods-material-ui/src/components/map/index.tsx index 41902a610774..12a661d4f201 100644 --- a/examples/finefoods-material-ui/src/components/map/index.tsx +++ b/examples/finefoods-material-ui/src/components/map/index.tsx @@ -1,4 +1,5 @@ import Map from "./map"; import MapMarker from "./marker"; +import AdvancedMarker from "./advanced-marker"; -export { MapMarker, Map }; +export { MapMarker, Map, AdvancedMarker }; diff --git a/examples/finefoods-material-ui/src/components/map/map.tsx b/examples/finefoods-material-ui/src/components/map/map.tsx index e0992a79d97f..722b47278353 100644 --- a/examples/finefoods-material-ui/src/components/map/map.tsx +++ b/examples/finefoods-material-ui/src/components/map/map.tsx @@ -1,9 +1,11 @@ import { Children, cloneElement, + Dispatch, FC, isValidElement, PropsWithChildren, + SetStateAction, useEffect, useRef, useState, @@ -11,27 +13,49 @@ import { import { Wrapper } from "@googlemaps/react-wrapper"; interface MapProps extends Exclude { + setMap?: Dispatch>; center?: google.maps.LatLngLiteral; + onDragStart?: Function; } const MapComponent: FC> = ({ children, center, zoom = 12, + onDragStart, + mapId, + setMap: setMapFromProps, ...options }) => { const ref = useRef(null); const [map, setMap] = useState(); + useEffect(() => { + if (map && center) { + map?.setCenter({ + lat: center.lat, + lng: center.lng, + }); + } + }, [center]); + useEffect(() => { if (map) { map.setOptions({ ...options, zoom, center }); + setMapFromProps?.(map); + if (onDragStart) { + map.addListener("dragstart", onDragStart); + } } }, [map]); useEffect(() => { if (ref.current && !map) { - setMap(new window.google.maps.Map(ref.current, {})); + const mapContructor = new window.google.maps.Map(ref.current, { + mapId, + }); + setMap(mapContructor); + setMapFromProps?.(mapContructor); } }, [ref, map]); @@ -48,10 +72,23 @@ const MapComponent: FC> = ({ ); }; -const MapWrapper: FC> = (props) => { +type MapWrapperProps = { + mapProps?: MapProps; +}; + +const MapWrapper: FC> = ({ + children, + mapProps, +}) => { return ( - - + + {children} ); }; diff --git a/examples/finefoods-material-ui/src/components/map/marker.tsx b/examples/finefoods-material-ui/src/components/map/marker.tsx index 8f96ac338fea..b9c913e1c455 100644 --- a/examples/finefoods-material-ui/src/components/map/marker.tsx +++ b/examples/finefoods-material-ui/src/components/map/marker.tsx @@ -1,10 +1,11 @@ import { useState, useEffect, memo } from "react"; interface MarkerProps extends google.maps.MarkerOptions { - onClick?: () => void; + onClick?: Function; + onDragEnd?: Function; } -const Marker: React.FC = ({ onClick, ...options }) => { +const Marker: React.FC = ({ onClick, onDragEnd, ...options }) => { const [marker, setMarker] = useState(); useEffect(() => { @@ -25,10 +26,14 @@ const Marker: React.FC = ({ onClick, ...options }) => { marker.setOptions({ ...options, clickable: !!onClick, + draggable: !!onDragEnd, }); - marker.addListener("click", () => { - onClick?.(); - }); + if (onClick) { + marker.addListener("click", onClick); + } + if (onDragEnd) { + marker.addListener("dragend", onDragEnd); + } } return () => { diff --git a/examples/finefoods-material-ui/src/components/offLayoutArea/index.tsx b/examples/finefoods-material-ui/src/components/offLayoutArea/index.tsx deleted file mode 100644 index 0cd4292f1ab3..000000000000 --- a/examples/finefoods-material-ui/src/components/offLayoutArea/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { RefineKbar } from "@refinedev/kbar"; -import ArrowLeftOutlined from "@mui/icons-material/ArrowLeftOutlined"; - -import "./style.css"; - -export const OffLayoutArea: React.FC = () => { - return ( - <> - - - - ); -}; diff --git a/examples/finefoods-material-ui/src/components/offLayoutArea/style.css b/examples/finefoods-material-ui/src/components/offLayoutArea/style.css deleted file mode 100644 index a52f28ffa349..000000000000 --- a/examples/finefoods-material-ui/src/components/offLayoutArea/style.css +++ /dev/null @@ -1,47 +0,0 @@ -@keyframes bounce { - from { - transform: scale(1) translateX(0); - } - - to { - transform: scale(1.3) translateX(-2px); - } -} - -.toggle-container { - position: fixed; - top: calc(50% - 28px); - right: -25px; - transform: translateX(101px); - background-color: #fb7a32; - color: white; - padding: 3px 6px; - text-align: center; - border-top-left-radius: 99px; - border-bottom-left-radius: 99px; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - transition: transform 0.8s ease-out; - z-index: 2; -} - -.toggle-container:hover { - transform: translateX(0px); - right: 0px; -} -a { - color: white; - transform: translateX(0px); - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -.icon { - color: white; - animation: bounce 1s alternate; - animation-iteration-count: infinite; -} - diff --git a/examples/finefoods-material-ui/src/components/order/details/index.tsx b/examples/finefoods-material-ui/src/components/order/details/index.tsx new file mode 100644 index 000000000000..64ee21192788 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/order/details/index.tsx @@ -0,0 +1,155 @@ +import { ReactNode } from "react"; +import dayjs from "dayjs"; +import { DateField } from "@refinedev/mui"; +import { useTranslate } from "@refinedev/core"; +import Box from "@mui/material/Box"; +import Stepper from "@mui/material/Stepper"; +import Step from "@mui/material/Step"; +import StepLabel from "@mui/material/StepLabel"; +import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; +import { useTheme } from "@mui/material/styles"; +import Avatar from "@mui/material/Avatar"; +import HistoryToggleOffOutlinedIcon from "@mui/icons-material/HistoryToggleOffOutlined"; +import StoreOutlinedIcon from "@mui/icons-material/StoreOutlined"; +import MopedOutlinedIcon from "@mui/icons-material/MopedOutlined"; +import LocalPhoneOutlinedIcon from "@mui/icons-material/LocalPhoneOutlined"; +import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined"; +import WatchLaterOutlinedIcon from "@mui/icons-material/WatchLaterOutlined"; +import { IEvent, IOrder } from "../../../interfaces"; +import Skeleton from "@mui/material/Skeleton"; + +type Props = { + order?: IOrder; +}; + +export const OrderDetails = ({ order }: Props) => { + const { palette } = useTheme(); + const t = useTranslate(); + + return ( + + el.status === order?.status?.text, + )} + > + {order?.events.map((event: IEvent, index: number) => ( + + + {event.date && dayjs(event.date).format("L LT")} + + } + error={event.status === "Cancelled"} + > + {event.status} + + + ))} + + + + } + label={t("orders.fields.deliveryTime")} + value={ + order?.createdAt && + dayjs(order.createdAt).add(30, "minutes").format("HH:mm A") + } + /> + + } + label={t("orders.fields.store")} + value={order?.store?.title} + /> + + } + label={t("orders.fields.courier")} + value={ + + + {order?.courier?.name} + + } + /> + + } + label={t("orders.fields.phone")} + value={order?.courier?.gsm} + /> + + } + label={t("orders.fields.customer")} + value={order?.user?.fullName} + /> + + } + label={t("orders.fields.createdAt")} + value={ + order?.createdAt && ( + + ) + } + /> + + ); +}; + +type InfoProps = { + icon: ReactNode; + label: string; + value?: ReactNode; +}; + +const Info = ({ icon, label, value }: InfoProps) => { + const { palette } = useTheme(); + + return ( + + + {icon} + + + {label} + + + {value ?? ( + + )} + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/order/index.ts b/examples/finefoods-material-ui/src/components/order/index.ts new file mode 100644 index 000000000000..91af32df9a00 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/order/index.ts @@ -0,0 +1,5 @@ +export * from "./status"; +export * from "./tableColumnProducts"; +export * from "./details"; +export * from "./map"; +export * from "./products"; diff --git a/examples/finefoods-material-ui/src/components/order/map/index.tsx b/examples/finefoods-material-ui/src/components/order/map/index.tsx new file mode 100644 index 000000000000..ca47d4b991ff --- /dev/null +++ b/examples/finefoods-material-ui/src/components/order/map/index.tsx @@ -0,0 +1,41 @@ +import { Map, MapMarker } from "../../map"; +import { IOrder } from "../../../interfaces"; + +type Props = { + order?: IOrder; +}; + +export const OrderDeliveryMap = ({ order }: Props) => { + return ( + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/order/products/index.tsx b/examples/finefoods-material-ui/src/components/order/products/index.tsx new file mode 100644 index 000000000000..5d22e931d789 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/order/products/index.tsx @@ -0,0 +1,143 @@ +import { useMemo } from "react"; +import { NumberField } from "@refinedev/mui"; +import { useTranslate } from "@refinedev/core"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import Typography from "@mui/material/Typography"; +import Avatar from "@mui/material/Avatar"; +import Divider from "@mui/material/Divider"; +import Box from "@mui/material/Box"; +import { IOrder, IProduct } from "../../../interfaces"; +import { getUniqueListWithCount } from "../../../utils"; + +type Props = { + order?: IOrder; +}; + +export const OrderProducts = ({ order }: Props) => { + const t = useTranslate(); + + const products = order?.products || []; + const uniqueProducts = getUniqueListWithCount({ + list: products, + field: "id", + }); + + const columns = useMemo[]>( + () => [ + { + field: "avatar", + headerName: t("products.fields.images.label"), + renderCell: function render({ row }) { + return ( + + ); + }, + width: 64, + align: "center", + headerAlign: "center", + sortable: false, + }, + { + field: "name", + headerName: t("products.fields.name"), + flex: 1, + sortable: false, + }, + { + field: "count", + headerName: "Quantity", + align: "right", + headerAlign: "right", + sortable: false, + }, + { + field: "price", + headerName: t("products.fields.price"), + width: 120, + sortable: false, + align: "right", + headerAlign: "right", + renderCell: function render({ row }) { + return ( + + ); + }, + }, + { + field: "total", + headerName: "Total", + sortable: false, + align: "right", + headerAlign: "right", + renderCell: function render({ row }) { + return ( + + ); + }, + }, + ], + [t], + ); + + return ( + { + return ( + <> + + + + Total + + acc + product.count * product.price, + 0, + )} + options={{ + style: "currency", + currency: "USD", + }} + /> + + + ); + }, + }} + /> + ); +}; diff --git a/examples/finefoods-material-ui/src/components/orderStatus/index.tsx b/examples/finefoods-material-ui/src/components/order/status/index.tsx similarity index 55% rename from examples/finefoods-material-ui/src/components/orderStatus/index.tsx rename to examples/finefoods-material-ui/src/components/order/status/index.tsx index cafc6f943ba1..1b6af2986430 100644 --- a/examples/finefoods-material-ui/src/components/orderStatus/index.tsx +++ b/examples/finefoods-material-ui/src/components/order/status/index.tsx @@ -1,33 +1,42 @@ import { useTranslate } from "@refinedev/core"; -import { useTheme } from "@mui/material/styles"; import Chip from "@mui/material/Chip"; import type { ChipProps } from "@mui/material/Chip"; +import CancelIcon from "@mui/icons-material/Cancel"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import WatchLaterIcon from "@mui/icons-material/WatchLater"; +import NotificationsIcon from "@mui/icons-material/Notifications"; +import MopedIcon from "@mui/icons-material/Moped"; type OrderStatusProps = { status?: "Pending" | "Ready" | "On The Way" | "Delivered" | "Cancelled"; }; -export const OrderStatus: React.FC = ({ status }) => { +export const OrderStatus = ({ status }: OrderStatusProps) => { const t = useTranslate(); - const { palette } = useTheme(); let color: ChipProps["color"]; + let icon: ChipProps["icon"]; switch (status) { case "Pending": color = "warning"; + icon = ; break; case "Ready": - color = "success"; + color = "default"; + icon = ; break; case "On The Way": color = "info"; + icon = ; break; case "Delivered": - color = palette.mode === "dark" ? "default" : "secondary"; + color = "success"; + icon = ; break; case "Cancelled": color = "error"; + icon = ; break; } @@ -35,6 +44,7 @@ export const OrderStatus: React.FC = ({ status }) => { diff --git a/examples/finefoods-material-ui/src/components/order/tableColumnProducts/index.tsx b/examples/finefoods-material-ui/src/components/order/tableColumnProducts/index.tsx new file mode 100644 index 000000000000..2be3d1fc214b --- /dev/null +++ b/examples/finefoods-material-ui/src/components/order/tableColumnProducts/index.tsx @@ -0,0 +1,52 @@ +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Avatar from "@mui/material/Avatar"; +import { CustomTooltip } from "../../customTooltip"; +import { IOrder } from "../../../interfaces"; +import { getUniqueListWithCount } from "../../../utils"; + +type Props = { + order: IOrder; +}; + +export const OrderTableColumnProducts = ({ order }: Props) => { + const uniqueProducts = getUniqueListWithCount({ + list: order?.products || [], + field: "id", + }); + const visibleProducts = uniqueProducts.slice(0, 3); + const unvisibleProducts = uniqueProducts.slice(3); + + return ( + + {visibleProducts.map((product) => { + const image = product.images?.[0]; + return ( + + + + ); + })} + {!!unvisibleProducts.length && ( + + +{unvisibleProducts.length} + + )} + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/product/categoryFilter.tsx b/examples/finefoods-material-ui/src/components/product/categoryFilter.tsx deleted file mode 100644 index 18b79aed4dc1..000000000000 --- a/examples/finefoods-material-ui/src/components/product/categoryFilter.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useState, useEffect } from "react"; -import { - CrudFilters, - getDefaultFilter, - useList, - useTranslate, -} from "@refinedev/core"; -import LoadingButton from "@mui/lab/LoadingButton"; -import Stack from "@mui/material/Stack"; -import Grid from "@mui/material/Grid"; - -import { ICategory } from "../../interfaces"; - -type ProductItemProps = { - setFilters: (filters: CrudFilters) => void; - filters: CrudFilters; -}; - -export const CategoryFilter: React.FC = ({ - setFilters, - filters, -}) => { - const t = useTranslate(); - - const [filterCategories, setFilterCategories] = useState( - getDefaultFilter("category.id", filters, "in") ?? [], - ); - - const { data: categories, isLoading } = useList({ - resource: "categories", - }); - - useEffect(() => { - setFilters?.([ - { - field: "category.id", - operator: "in", - value: filterCategories.length > 0 ? filterCategories : undefined, - }, - ]); - }, [filterCategories]); - - const toggleFilterCategory = (clickedCategory: string) => { - const target = filterCategories.findIndex( - (category) => category === clickedCategory, - ); - - if (target < 0) { - setFilterCategories((prevCategories) => { - return [...prevCategories, clickedCategory]; - }); - } else { - const copyFilterCategories = [...filterCategories]; - - copyFilterCategories.splice(target, 1); - - setFilterCategories(copyFilterCategories); - } - }; - - return ( - - - - setFilterCategories([])} - variant={filterCategories.length === 0 ? "contained" : "outlined"} - size="small" - loading={isLoading} - sx={{ - borderRadius: "50px", - }} - > - {t("stores.all")} - - - {categories?.data.map((category: ICategory) => ( - - toggleFilterCategory(category.id.toString())} - > - {category.title} - - - ))} - - - ); -}; diff --git a/examples/finefoods-material-ui/src/components/product/create.tsx b/examples/finefoods-material-ui/src/components/product/create.tsx deleted file mode 100644 index f6e3b5109144..000000000000 --- a/examples/finefoods-material-ui/src/components/product/create.tsx +++ /dev/null @@ -1,346 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { useTranslate, useApiUrl, HttpError } from "@refinedev/core"; -import { UseModalFormReturnType } from "@refinedev/react-hook-form"; -import { Controller } from "react-hook-form"; -import { Create, useAutocomplete } from "@refinedev/mui"; - -import Drawer from "@mui/material/Drawer"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Input from "@mui/material/Input"; -import Radio from "@mui/material/Radio"; -import RadioGroup from "@mui/material/RadioGroup"; -import Avatar from "@mui/material/Avatar"; -import Typography from "@mui/material/Typography"; -import FormLabel from "@mui/material/FormLabel"; -import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; -import IconButton from "@mui/material/IconButton"; -import FormControl from "@mui/material/FormControl"; -import Autocomplete from "@mui/material/Autocomplete"; -import OutlinedInput from "@mui/material/OutlinedInput"; -import InputAdornment from "@mui/material/InputAdornment"; -import FormHelperText from "@mui/material/FormHelperText"; -import TextField from "@mui/material/TextField"; - -import CloseOutlined from "@mui/icons-material/CloseOutlined"; - -import { ICategory, IFile, IProduct, Nullable } from "../../interfaces"; - -export const CreateProduct: React.FC< - UseModalFormReturnType> -> = ({ - watch, - setValue, - register, - formState: { errors }, - control, - refineCore: { onFinish }, - handleSubmit, - modal: { visible, close }, - saveButtonProps, -}) => { - const t = useTranslate(); - - const apiUrl = useApiUrl(); - - const { autocompleteProps } = useAutocomplete({ - resource: "categories", - }); - - const imageInput = watch("images"); - - const onChangeHandler = async ( - event: React.ChangeEvent, - ) => { - const formData = new FormData(); - - const target = event.target; - const file: File = (target.files as FileList)[0]; - - formData.append("file", file); - - const res = await axios.post<{ url: string }>( - `${apiUrl}/media/upload`, - formData, - { - withCredentials: false, - headers: { - "Access-Control-Allow-Origin": "*", - }, - }, - ); - - const { name, size, type, lastModified } = file; - - // eslint-disable-next-line - const imagePaylod: any = [ - { - name, - size, - type, - lastModified, - url: res.data.url, - }, - ]; - - setValue("images", imagePaylod, { shouldValidate: true }); - }; - - return ( - - close()} - sx={{ - width: "30px", - height: "30px", - mb: "5px", - }} - > - - - ), - action: null, - }} - wrapperProps={{ sx: { overflowY: "scroll", height: "100vh" } }} - > - - -
- - - {t("products.fields.images.label")} - - - - - {t("products.fields.images.description")} - - - {t("products.fields.images.validation")} - - - {errors.images && ( - {errors.images.message} - )} - - - - {t("products.fields.name")} - - {errors.name && ( - {errors.name.message} - )} - - - - {t("products.fields.description")} - - - {errors.description && ( - - {errors.description.message} - - )} - - - {t("products.fields.price")} - $ - } - /> - {errors.price && ( - - {errors.price.message} - - )} - - - ( - { - field.onChange(value); - }} - getOptionLabel={(item) => { - return item.title ? item.title : ""; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} - /> - )} - /> - {errors.category && ( - - {errors.category.message} - - )} - - - - {t("products.fields.isActive")} - - ( - { - const value = event.target.value === "true"; - - setValue("isActive", value, { - shouldValidate: true, - }); - - return value; - }} - row - > - } - label={t("status.enable")} - /> - } - label={t("status.disable")} - /> - - )} - /> - {errors.isActive && ( - - {errors.isActive.message} - - )} - - -
-
-
-
-
- ); -}; diff --git a/examples/finefoods-material-ui/src/components/product/drawer-form/index.tsx b/examples/finefoods-material-ui/src/components/product/drawer-form/index.tsx new file mode 100644 index 000000000000..b7176b2cb983 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/product/drawer-form/index.tsx @@ -0,0 +1,366 @@ +import { + HttpError, + useApiUrl, + useGetToPath, + useGo, + useTranslate, +} from "@refinedev/core"; +import { DeleteButton, useAutocomplete } from "@refinedev/mui"; +import { useSearchParams } from "react-router-dom"; +import { useForm } from "@refinedev/react-hook-form"; +import { Controller } from "react-hook-form"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import FormControl from "@mui/material/FormControl"; +import FormHelperText from "@mui/material/FormHelperText"; +import TextField from "@mui/material/TextField"; +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import InputAdornment from "@mui/material/InputAdornment"; +import Autocomplete from "@mui/material/Autocomplete"; +import ToggleButton from "@mui/material/ToggleButton"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; +import FormLabel from "@mui/material/FormLabel"; +import { Drawer, DrawerHeader, ProductImageUpload } from "../../../components"; +import { useImageUpload } from "../../../utils"; +import { ICategory, IFile, IProduct, Nullable } from "../../../interfaces"; + +type Props = { + action: "create" | "edit"; +}; + +export const ProductDrawerForm = (props: Props) => { + const getToPath = useGetToPath(); + const [searchParams] = useSearchParams(); + const go = useGo(); + const t = useTranslate(); + const apiUrl = useApiUrl(); + + const onDrawerCLose = () => { + go({ + to: + searchParams.get("to") ?? + getToPath({ + action: "list", + }) ?? + "", + query: { + to: undefined, + }, + options: { + keepQuery: true, + }, + type: "replace", + }); + }; + + const { + watch, + control, + setValue, + handleSubmit, + formState: { errors }, + refineCore: { onFinish, id, formLoading }, + saveButtonProps, + } = useForm>({ + refineCoreProps: { + redirect: false, + onMutationSuccess: () => { + if (props.action === "create") { + onDrawerCLose(); + } + }, + }, + }); + const imageInput: IFile[] | null = watch("images"); + + const { autocompleteProps } = useAutocomplete({ + resource: "categories", + }); + + const imageUploadOnChangeHandler = async ( + event: React.ChangeEvent, + ) => { + const target = event.target; + const file: File = (target.files as FileList)[0]; + + const image = await useImageUpload({ + apiUrl, + file, + }); + + setValue("images", image, { shouldValidate: true }); + }; + + return ( + + +
{ + onFinish(data); + })} + > + + { + return ( + + ); + }} + /> + + {errors.images && ( + {errors.images.message} + )} + + + + + + { + return ( + + ); + }} + /> + {errors.name && ( + {errors.name.message} + )} + + + { + return ( + + ); + }} + /> + {errors.description && ( + + {errors.description.message} + + )} + + + { + return ( + $ + ), + }} + /> + ); + }} + /> + {errors.price && ( + {errors.price.message} + )} + + + ( + + id="category" + {...autocompleteProps} + {...field} + onChange={(_, value) => { + field.onChange(value); + }} + getOptionLabel={(item) => { + return ( + autocompleteProps?.options?.find( + (p) => p?.id?.toString() === item?.id?.toString(), + )?.title ?? "" + ); + }} + isOptionEqualToValue={(option, value) => + value === undefined || + option?.id?.toString() === + (value?.id ?? value)?.toString() + } + renderInput={(params) => ( + + )} + /> + )} + /> + {errors.category && ( + {errors.category.message} + )} + + + + {t("products.fields.isActive.label")} + { + if (value === undefined) { + return t("errors.required.field", { + field: "isActive", + }); + } + return true; + }, + }} + defaultValue={false} + render={({ field }) => ( + { + setValue("isActive", newValue, { + shouldValidate: true, + }); + + return newValue; + }} + > + + {t("products.fields.isActive.true")} + + + {t("products.fields.isActive.false")} + + + )} + /> + {errors.isActive && ( + {errors.isActive.message} + )} + + + + + + {props.action === "edit" && ( + { + onDrawerCLose(); + }} + /> + )} + + +
+
+ ); +}; diff --git a/examples/finefoods-material-ui/src/components/product/edit.tsx b/examples/finefoods-material-ui/src/components/product/edit.tsx deleted file mode 100644 index 6a828d87395a..000000000000 --- a/examples/finefoods-material-ui/src/components/product/edit.tsx +++ /dev/null @@ -1,352 +0,0 @@ -import React from "react"; -import axios from "axios"; -import { useTranslate, useApiUrl, HttpError } from "@refinedev/core"; -import { useAutocomplete, Edit } from "@refinedev/mui"; -import { UseModalFormReturnType } from "@refinedev/react-hook-form"; -import { Controller } from "react-hook-form"; - -import Drawer from "@mui/material/Drawer"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Input from "@mui/material/Input"; -import Radio from "@mui/material/Radio"; -import RadioGroup from "@mui/material/RadioGroup"; -import TextField from "@mui/material/TextField"; -import Avatar from "@mui/material/Avatar"; -import Typography from "@mui/material/Typography"; -import FormLabel from "@mui/material/FormLabel"; -import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; -import IconButton from "@mui/material/IconButton"; -import FormControl from "@mui/material/FormControl"; -import OutlinedInput from "@mui/material/OutlinedInput"; -import InputAdornment from "@mui/material/InputAdornment"; -import FormHelperText from "@mui/material/FormHelperText"; -import Autocomplete from "@mui/material/Autocomplete"; - -import CloseIcon from "@mui/icons-material/Close"; - -import { ICategory, IFile, IProduct, Nullable } from "../../interfaces"; - -export const EditProduct: React.FC< - UseModalFormReturnType> -> = ({ - watch, - setValue, - register, - formState: { errors }, - control, - refineCore: { onFinish }, - handleSubmit, - modal: { visible, close }, - saveButtonProps, - getValues, -}) => { - const t = useTranslate(); - - const apiUrl = useApiUrl(); - - const { autocompleteProps } = useAutocomplete({ - resource: "categories", - }); - - const imageInput = watch("images"); - - const onChangeHandler = async ( - event: React.ChangeEvent, - ) => { - const formData = new FormData(); - - const target = event.target; - const file: File = (target.files as FileList)[0]; - - formData.append("file", file); - - const res = await axios.post<{ url: string }>( - `${apiUrl}/media/upload`, - formData, - { - withCredentials: false, - headers: { - "Access-Control-Allow-Origin": "*", - }, - }, - ); - - const { name, size, type, lastModified } = file; - - // eslint-disable-next-line - const imagePaylod: any = [ - { - name, - size, - type, - lastModified, - url: res.data.url, - }, - ]; - - setValue("images", imagePaylod, { shouldValidate: true }); - }; - - return ( - - close()} - sx={{ width: "30px", height: "30px", mb: "5px" }} - > - - - ), - action: null, - }} - wrapperProps={{ sx: { overflowY: "scroll", height: "100vh" } }} - > - - -
- - - {t("products.fields.images.label")} - - - - - {t("products.fields.images.description")} - - - {t("products.fields.images.validation")} - - - {errors.images && ( - {errors.images.message} - )} - - - - {t("products.fields.name")} - - {errors.name && ( - {errors.name.message} - )} - - - - {t("products.fields.description")} - - - {errors.description && ( - - {errors.description.message} - - )} - - - {t("products.fields.price")} - $ - } - /> - {errors.price && ( - - {errors.price.message} - - )} - - - ( - { - field.onChange(value); - }} - getOptionLabel={(item) => { - return item.title - ? item.title - : autocompleteProps?.options?.find( - (p) => p.id.toString() === item.toString(), - )?.title ?? ""; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} - /> - )} - /> - {errors.category && ( - - {errors.category.message} - - )} - - - - {t("products.fields.isActive")} - - { - return ( - { - const value = event.target.value === "true"; - - setValue("isActive", value, { - shouldValidate: true, - }); - - return value; - }} - row - > - } - label={t("status.enable")} - /> - } - label={t("status.disable")} - /> - - ); - }} - /> - {errors.isActive && ( - - {errors.isActive.message} - - )} - - -
-
-
-
-
- ); -}; diff --git a/examples/finefoods-material-ui/src/components/product/image-upload/index.tsx b/examples/finefoods-material-ui/src/components/product/image-upload/index.tsx new file mode 100644 index 000000000000..b4a732a2277b --- /dev/null +++ b/examples/finefoods-material-ui/src/components/product/image-upload/index.tsx @@ -0,0 +1,91 @@ +import { useTranslate } from "@refinedev/core"; +import { SxProps, styled, useTheme } from "@mui/material/styles"; +import PhotoOutlinedIcon from "@mui/icons-material/PhotoOutlined"; +import CloudUploadOutlinedIcon from "@mui/icons-material/CloudUploadOutlined"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; + +type Props = { + previewURL?: string; + sx?: SxProps; + inputProps?: React.InputHTMLAttributes; +}; + +export const ProductImageUpload = (props: Props) => { + const t = useTranslate(); + const theme = useTheme(); + + const isDarkMode = theme.palette.mode === "dark"; + + return ( + + {props.previewURL ? ( + + ) : ( + + )} + + + ); +}; + +const VisuallyHiddenInput = styled("input")({ + clip: "rect(0 0 0 0)", + clipPath: "inset(50%)", + height: 1, + overflow: "hidden", + position: "absolute", + bottom: 0, + left: 0, + whiteSpace: "nowrap", + width: 1, +}); diff --git a/examples/finefoods-material-ui/src/components/product/index.tsx b/examples/finefoods-material-ui/src/components/product/index.tsx index 5ba8e74d9447..5361d950a1e8 100644 --- a/examples/finefoods-material-ui/src/components/product/index.tsx +++ b/examples/finefoods-material-ui/src/components/product/index.tsx @@ -1,4 +1,6 @@ -export { ProductItem } from "./item"; -export { CategoryFilter } from "./categoryFilter"; -export { CreateProduct } from "./create"; -export { EditProduct } from "./edit"; +export * from "./list-table"; +export * from "./list-card"; +export * from "./status"; +export * from "./drawer-form"; +export * from "./status"; +export * from "./image-upload"; diff --git a/examples/finefoods-material-ui/src/components/product/item.tsx b/examples/finefoods-material-ui/src/components/product/item.tsx deleted file mode 100644 index 7e456828cf18..000000000000 --- a/examples/finefoods-material-ui/src/components/product/item.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useState } from "react"; -import { useTranslate, BaseKey } from "@refinedev/core"; - -import Card from "@mui/material/Card"; -import CardHeader from "@mui/material/CardHeader"; -import Box from "@mui/material/Box"; -import IconButton from "@mui/material/IconButton"; -import CardMedia from "@mui/material/CardMedia"; -import CardContent from "@mui/material/CardContent"; -import Typography from "@mui/material/Typography"; -import Tooltip from "@mui/material/Tooltip"; -import Popover from "@mui/material/Popover"; -import Button from "@mui/material/Button"; -import Divider from "@mui/material/Divider"; -import TextField from "@mui/material/TextField"; -import MoreVertIcon from "@mui/icons-material/MoreVert"; -import EditIcon from "@mui/icons-material/Edit"; - -import { IProduct } from "../../interfaces"; - -type PropductItem = { - updateStock?: (changedValue: number, clickedProduct: IProduct) => void; - product: IProduct; - show: (id: BaseKey) => void; -}; - -export const ProductItem: React.FC = ({ - product, - show, - updateStock, -}) => { - const t = useTranslate(); - const { id, name, description, images, price } = product; - - const [anchorEl, setAnchorEl] = useState(null); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const open = Boolean(anchorEl); - const popoverId = open ? "simple-popover" : undefined; - - return ( - - - - - - - - - - } - sx={{ padding: 0 }} - /> - - - - - - - - {name} - - - - - {description} - - - {`#10000${id}`} - - {`${price / 100}$`} - - {updateStock && ( - { - e.preventDefault(); - updateStock(parseInt(e.target.value, 10), product); - }} - /> - )} - - - ); -}; diff --git a/examples/finefoods-material-ui/src/components/product/list-card/index.tsx b/examples/finefoods-material-ui/src/components/product/list-card/index.tsx new file mode 100644 index 000000000000..3bdeefc235bc --- /dev/null +++ b/examples/finefoods-material-ui/src/components/product/list-card/index.tsx @@ -0,0 +1,245 @@ +import { useGo, useNavigation, useTranslate } from "@refinedev/core"; +import { NumberField, UseDataGridReturnType } from "@refinedev/mui"; +import Typography from "@mui/material/Typography"; +import { ICategory, IProduct } from "../../../interfaces"; +import { useLocation } from "react-router-dom"; +import { ProductStatus } from "../status"; +import Grid from "@mui/material/Grid"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardMedia from "@mui/material/CardMedia"; +import CardActionArea from "@mui/material/CardActionArea"; +import Stack from "@mui/material/Stack"; +import CardActions from "@mui/material/CardActions"; +import Divider from "@mui/material/Divider"; +import Chip from "@mui/material/Chip"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; +import TablePagination from "@mui/material/TablePagination"; +import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined"; +import { useMemo } from "react"; + +type Props = { + categories: ICategory[]; +} & UseDataGridReturnType; + +export const ProductListCard = (props: Props) => { + const go = useGo(); + const { pathname } = useLocation(); + const { editUrl } = useNavigation(); + const t = useTranslate(); + const products = props.tableQueryResult?.data?.data || []; + + const categoryFilters = useMemo(() => { + const filter = props.filters.find((filter) => { + if ("field" in filter) { + return filter.field === "category.id"; + } + + return false; + }); + + const filterValues = filter?.value?.map((value: string | number) => + Number(value), + ); + + return { + operator: filter?.operator || "in", + value: (filterValues || []) as number[], + }; + }, [props.filters]).value; + + const hasCategoryFilter = categoryFilters?.length > 0; + + const handleOnTagClick = (categoryId: number) => { + const newFilters = categoryFilters; + const hasCurrentFilter = newFilters.includes(categoryId); + if (hasCurrentFilter) { + newFilters.splice(newFilters.indexOf(categoryId), 1); + } else { + newFilters.push(categoryId); + } + + props.setFilters([ + { + field: "category.id", + operator: "in", + value: newFilters, + }, + ]); + }; + + return ( + <> + + + { + props.setFilters([ + { + field: "category.id", + operator: "in", + value: [], + }, + ]); + }} + /> + {props.categories.map((category) => { + return ( + { + handleOnTagClick(category.id); + }} + /> + ); + })} + + + + {products?.map((product) => { + const category = props.categories.find( + (c) => c.id === product.category.id, + ); + + return ( + + + + + + + + + + + + {product.name} + + + + + {product.description} + + + + + + + + + + + ); + })} + + + { + props.setCurrent(page); + }} + rowsPerPage={props.pageSize} + rowsPerPageOptions={[12, 24, 48, 96]} + onRowsPerPageChange={(e) => { + props.setPageSize(+e.target.value); + }} + /> + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/product/list-table/index.tsx b/examples/finefoods-material-ui/src/components/product/list-table/index.tsx new file mode 100644 index 000000000000..7f1fb1693a88 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/product/list-table/index.tsx @@ -0,0 +1,152 @@ +import React from "react"; +import { useGo, useNavigation, useTranslate } from "@refinedev/core"; +import { NumberField, UseDataGridReturnType } from "@refinedev/mui"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import Typography from "@mui/material/Typography"; +import Avatar from "@mui/material/Avatar"; +import { ICategory, IProduct } from "../../../interfaces"; +import { useLocation } from "react-router-dom"; +import IconButton from "@mui/material/IconButton"; +import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; +import { ProductStatus } from "../status"; + +type Props = { + categories: ICategory[]; +} & UseDataGridReturnType; + +export const ProductListTable = (props: Props) => { + const go = useGo(); + const { pathname } = useLocation(); + const { editUrl } = useNavigation(); + const t = useTranslate(); + + const columns = React.useMemo[]>( + () => [ + { + field: "id", + headerName: "ID #", + description: "ID #", + width: 52, + renderCell: function render({ row }) { + return #{row.id}; + }, + }, + { + field: "avatar", + headerName: t("products.fields.images.label"), + renderCell: function render({ row }) { + return ( + + ); + }, + width: 64, + align: "center", + headerAlign: "center", + sortable: false, + }, + { + field: "name", + headerName: t("products.fields.name"), + width: 200, + sortable: false, + }, + { + field: "description", + headerName: t("products.fields.description"), + minWidth: 320, + flex: 1, + sortable: false, + }, + { + field: "price", + headerName: t("products.fields.price"), + width: 120, + sortable: false, + align: "right", + headerAlign: "right", + renderCell: function render({ row }) { + return ( + + ); + }, + }, + { + field: "category.title", + headerName: t("products.fields.category"), + minWidth: 160, + sortable: false, + filterable: false, + renderCell: function render({ row }) { + const category = props.categories.find( + (category) => category.id === row.category.id, + ); + + return {category?.title}; + }, + }, + { + field: "isActive", + headerName: t("products.fields.isActive.label"), + minWidth: 136, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "actions", + headerName: t("table.actions"), + width: 80, + align: "center", + headerAlign: "center", + renderCell: function render({ row }) { + return ( + { + return go({ + to: `${editUrl("products", row.id)}`, + query: { + to: pathname, + }, + options: { + keepQuery: true, + }, + type: "replace", + }); + }} + > + + + ); + }, + }, + ], + [t, props.categories], + ); + + return ( + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/product/status/index.tsx b/examples/finefoods-material-ui/src/components/product/status/index.tsx new file mode 100644 index 000000000000..f60b66f407aa --- /dev/null +++ b/examples/finefoods-material-ui/src/components/product/status/index.tsx @@ -0,0 +1,24 @@ +import Chip, { ChipProps } from "@mui/material/Chip"; +import { useTranslate } from "@refinedev/core"; +import { IProduct } from "../../../interfaces"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import BlockOutlinedIcon from "@mui/icons-material/BlockOutlined"; + +type Props = { + value: IProduct["isActive"]; + size?: ChipProps["size"]; +}; + +export const ProductStatus = (props: Props) => { + const t = useTranslate(); + + return ( + : } + variant="outlined" + size={props.size} + /> + ); +}; diff --git a/examples/finefoods-material-ui/src/components/refine-list-view/index.tsx b/examples/finefoods-material-ui/src/components/refine-list-view/index.tsx new file mode 100644 index 000000000000..63207b37b60d --- /dev/null +++ b/examples/finefoods-material-ui/src/components/refine-list-view/index.tsx @@ -0,0 +1,36 @@ +import { List, ListProps } from "@refinedev/mui"; + +type Props = {} & ListProps; + +export const RefineListView = ({ children, ...props }: Props) => { + return ( + + {children} + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/store/courier-table/index.tsx b/examples/finefoods-material-ui/src/components/store/courier-table/index.tsx new file mode 100644 index 000000000000..11cff5a2d9bf --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/courier-table/index.tsx @@ -0,0 +1,99 @@ +import { useMemo } from "react"; +import { useNavigation, useTranslate } from "@refinedev/core"; +import { useDataGrid } from "@refinedev/mui"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import Avatar from "@mui/material/Avatar"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { ICourier, IStore } from "../../../interfaces"; +import { CourierRating, CourierStatus } from "../../courier"; + +type Props = { + store?: IStore; +}; + +export const StoreCourierTable = (props: Props) => { + const t = useTranslate(); + const { edit } = useNavigation(); + + const { dataGridProps } = useDataGrid({ + resource: "couriers", + filters: { + permanent: [ + { + field: "store.id", + value: props.store?.id, + operator: "eq", + }, + ], + }, + pagination: { + mode: "off", + }, + queryOptions: { + enabled: !!props.store?.id, + }, + }); + + const columns = useMemo[]>( + () => [ + { + field: "name", + headerName: t("couriers.couriers"), + renderCell: function render({ row }) { + return ( + + + + {row.name} {row.surname} + + + ); + }, + flex: 1, + }, + { + field: "licensePlate", + headerName: t("couriers.fields.licensePlate.label"), + }, + { + field: "rating", + headerName: t("couriers.fields.rating.label"), + width: 172, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "status", + headerName: t("couriers.fields.status.label"), + width: 140, + renderCell: function render({ row }) { + return ; + }, + }, + ], + [t], + ); + + return ( + { + edit("couriers", row.id); + }} + /> + ); +}; diff --git a/examples/finefoods-material-ui/src/components/store/form/index.tsx b/examples/finefoods-material-ui/src/components/store/form/index.tsx new file mode 100644 index 000000000000..3d7e14e3251c --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/form/index.tsx @@ -0,0 +1,304 @@ +import { useNavigation, useTranslate } from "@refinedev/core"; +import { DeleteButton, ListButton } from "@refinedev/mui"; +import { Controller } from "react-hook-form"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import Divider from "@mui/material/Divider"; +import FormControl from "@mui/material/FormControl"; +import FormHelperText from "@mui/material/FormHelperText"; +import FormLabel from "@mui/material/FormLabel"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import ToggleButton from "@mui/material/ToggleButton"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; +import ArrowBack from "@mui/icons-material/ArrowBack"; +import Stack from "@mui/material/Stack"; +import Button from "@mui/material/Button"; +import InputMask from "react-input-mask"; +import { useStoreForm } from "./useStoreForm"; +import { StoreMap } from "../map/store-map"; +import { StoreCourierTable } from "../courier-table"; + +type Props = { + action: "create" | "edit"; +}; + +export const StoreForm = (props: Props) => { + const t = useTranslate(); + const { list } = useNavigation(); + const { + register, + control, + formState: { errors }, + saveButtonProps, + setValue, + latLng, + store, + handleMapOnDragEnd, + handleAddressChange, + } = useStoreForm({ action: props.action }); + + return ( + <> + } + /> + + + +
+ + + ( + + )} + /> + {errors.title && ( + {errors.title.message} + )} + + + + + {t("products.fields.isActive.label")} + + { + if (value === undefined) { + return t("errors.required.field", { + field: "isActive", + }); + } + return true; + }, + }} + render={({ field }) => { + return ( + { + setValue("isActive", newValue, { + shouldValidate: true, + }); + + return newValue; + }} + > + + {t("stores.fields.isActive.true")} + + + {t("stores.fields.isActive.false")} + + + ); + }} + /> + {errors.isActive && ( + + {errors.isActive.message} + + )} + + + + ( + + )} + /> + {errors.email && ( + {errors.email.message} + )} + + + + ( + { + field.onChange(e); + handleAddressChange(e.target.value); + }} + /> + )} + /> + {errors.address && ( + + {errors.address.message} + + )} + + + + ( + + {/* @ts-expect-error False alarm */} + {(props: TextFieldProps) => ( + + )} + + )} + /> + {errors.gsm && ( + {errors.gsm.message} + )} + + + {/* this is a workaround for registering fields to ant design form*/} + {/* otherwise these fields will be null */} + + + + + + + {props.action === "edit" && ( + { + list({ name: "stores" }); + }} + /> + )} + + + +
+
+ + + {props.action !== "create" && ( + + + + )} + +
+ + ); +}; diff --git a/examples/finefoods-material-ui/src/components/store/form/useStoreForm.tsx b/examples/finefoods-material-ui/src/components/store/form/useStoreForm.tsx new file mode 100644 index 000000000000..10f3636307ad --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/form/useStoreForm.tsx @@ -0,0 +1,94 @@ +import { useForm } from "@refinedev/react-hook-form"; +import { useDebounceValue } from "usehooks-ts"; +import { + convertLatLng, + LatLng, + getAddressWithLatLng, + getLatLngWithAddress, +} from "../../../utils"; +import { IStore } from "../../../interfaces"; +import { useEffect, useState } from "react"; +import { HttpError } from "@refinedev/core"; + +type Props = { + action: "create" | "edit"; +}; + +export const useStoreForm = (props: Props) => { + const form = useForm({ + refineCoreProps: { + action: props.action, + redirect: false, + }, + }); + const store = form.refineCore.queryResult?.data?.data; + + const [latLng, setLatLng] = useState>({ + lat: props.action === "create" ? 39.66853 : undefined, + lng: props.action === "create" ? -75.67602 : undefined, + }); + + useEffect(() => { + if (store?.address?.coordinate) { + setLatLng({ + lat: Number(store.address.coordinate?.[0]), + lng: Number(store.address.coordinate?.[1]), + }); + } + }, [store?.address.coordinate?.[0], store?.address.coordinate?.[1]]); + + // we are using these debounced values to get lang and lat from the address text + // to minimize the number of requests, we are using debounced values + const [debouncedAdressValue, setDebouncedAdressValue] = useDebounceValue( + form?.getValues("address.text"), + 500, + ); + // get lat and lng with address + useEffect(() => { + if (debouncedAdressValue) { + getLatLngWithAddress(debouncedAdressValue).then((data) => { + // set form field with lat and lng values + if (data) { + const { lat, lng } = convertLatLng({ + lat: data.lat, + lng: data.lng, + }); + + form.setValue("address.coordinate", [lat, lng]); + + setLatLng({ + lat, + lng, + }); + } + }); + } + }, [debouncedAdressValue, form.setValue]); + + const handleMapOnDragEnd = async ({ + lat, + lng, + }: { + lat: number; + lng: number; + }) => { + // get address with lat lng and set form field + const data = await getAddressWithLatLng({ lat, lng }); + if (data) { + // set form field with address value + form.setValue("address.text", data.address); + } + }; + + const isLoading = + form.refineCore?.queryResult?.isFetching || form.refineCore.formLoading; + + return { + ...form, + store, + formLoading: isLoading, + latLng, + handleAddressChange: (address: string) => setDebouncedAdressValue(address), + handleMapOnDragEnd, + }; +}; diff --git a/examples/finefoods-material-ui/src/components/store/index.tsx b/examples/finefoods-material-ui/src/components/store/index.tsx index 59adccb1f650..bcad172a817f 100644 --- a/examples/finefoods-material-ui/src/components/store/index.tsx +++ b/examples/finefoods-material-ui/src/components/store/index.tsx @@ -1 +1,5 @@ -export * from "./storeProducts"; +export * from "./status"; +export * from "./table"; +export * from "./map"; +export * from "./form"; +export * from "./courier-table"; diff --git a/examples/finefoods-material-ui/src/components/store/map/all-stores-map.tsx b/examples/finefoods-material-ui/src/components/store/map/all-stores-map.tsx new file mode 100644 index 000000000000..61fa071fd759 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/map/all-stores-map.tsx @@ -0,0 +1,134 @@ +import { useList, useNavigation, useTranslate } from "@refinedev/core"; +import { useRef, useState } from "react"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import Divider from "@mui/material/Divider"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import LocalPhoneOutlinedIcon from "@mui/icons-material/LocalPhoneOutlined"; +import PlaceOutlinedIcon from "@mui/icons-material/PlaceOutlined"; +import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined"; +import { Map, AdvancedMarker } from "../../map"; +import { StoreStatus } from "../status"; +import { IStore } from "../../../interfaces"; + +export const AllStoresMap = () => { + const t = useTranslate(); + const parentRef = useRef(null); + const [map, setMap] = useState(); + const [selectedStore, setSelectedStore] = useState(null); + const { edit } = useNavigation(); + + const { data: storeData } = useList({ + resource: "stores", + pagination: { + mode: "off", + }, + }); + const stores = storeData?.data || []; + + const handleMarkerClick = (e: any, store: IStore) => { + setSelectedStore(store); + }; + + return ( + + + {stores?.map((store) => { + const lat = Number(store.address?.coordinate?.[0]); + const lng = Number(store.address?.coordinate?.[1]); + + if (!lat || !lng) return null; + + return ( + { + handleMarkerClick(e, store); + }} + > + {(selectedStore?.id !== store.id || !selectedStore) && ( + {store.title} + )} + {selectedStore?.id === store.id && ( + { + e.stopPropagation(); + setSelectedStore(null); + }} + sx={{ + padding: "16px", + position: "relative", + marginBottom: "16px", + }} + > + { + edit("stores", selectedStore.id); + }} + display="flex" + justifyContent="space-between" + alignItems="center" + > + {store.title} + + + + + + + {store.address?.text} + + + + + {store.email} + + + + + {store.gsm} + + + + )} + + ); + })} + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/store/map/index.tsx b/examples/finefoods-material-ui/src/components/store/map/index.tsx new file mode 100644 index 000000000000..260c686eed6e --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/map/index.tsx @@ -0,0 +1,2 @@ +export * from "./all-stores-map"; +export * from "./store-map"; diff --git a/examples/finefoods-material-ui/src/components/store/map/store-map.tsx b/examples/finefoods-material-ui/src/components/store/map/store-map.tsx new file mode 100644 index 000000000000..c2b83efe1fde --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/map/store-map.tsx @@ -0,0 +1,78 @@ +import { useCallback } from "react"; +import _debounce from "lodash/debounce"; +import Box from "@mui/material/Box"; +import { Map, MapMarker } from "../../map"; +import { IStore } from "../../../interfaces"; +import { convertLatLng } from "../../../utils"; + +type Props = { + store?: IStore; + lat?: number; + lng?: number; + zoom?: number; + isDisabled?: boolean; + onDragEnd?: ({ lat, lng }: { lat: number; lng: number }) => void; +}; + +export const StoreMap = (props: Props) => { + const onDragEndDebounced = useCallback( + _debounce((lat, lng) => { + if (props.onDragEnd) { + props.onDragEnd({ lat, lng }); + } + }, 1000), + [], + ); + + const handleDragEnd = (e: any) => { + if (!props.onDragEnd) return; + + const { lat, lng } = convertLatLng({ + lat: e.latLng.lat(), + lng: e.latLng.lng(), + }); + onDragEndDebounced.cancel(); + onDragEndDebounced(lat, lng); + }; + + const lat = Number(props.lat); + const lng = Number(props.lng); + + return ( + + + {lat && lng && ( + + )} + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/store/status/index.tsx b/examples/finefoods-material-ui/src/components/store/status/index.tsx new file mode 100644 index 000000000000..39bd16cf7d1a --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/status/index.tsx @@ -0,0 +1,25 @@ +import Chip, { ChipProps } from "@mui/material/Chip"; +import { useTranslate } from "@refinedev/core"; +import { IStore } from "../../../interfaces"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import BlockOutlinedIcon from "@mui/icons-material/BlockOutlined"; + +type Props = { + value: IStore["isActive"]; + size?: ChipProps["size"]; + label?: string; +}; + +export const StoreStatus = (props: Props) => { + const t = useTranslate(); + + return ( + : } + variant="outlined" + size={props.size} + /> + ); +}; diff --git a/examples/finefoods-material-ui/src/components/store/storeProducts.tsx b/examples/finefoods-material-ui/src/components/store/storeProducts.tsx deleted file mode 100644 index 4483a383d0af..000000000000 --- a/examples/finefoods-material-ui/src/components/store/storeProducts.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import React from "react"; -import { useTranslate, useTable, useUpdate, HttpError } from "@refinedev/core"; -import { useModalForm } from "@refinedev/react-hook-form"; -import { CreateButton } from "@refinedev/mui"; - -import Grid from "@mui/material/Grid"; -import Paper from "@mui/material/Paper"; -import Typography from "@mui/material/Typography"; -import Box from "@mui/material/Box"; -import InputBase from "@mui/material/InputBase"; -import IconButton from "@mui/material/IconButton"; -import Stack from "@mui/material/Stack"; -import Pagination from "@mui/material/Pagination"; -import Modal from "@mui/material/Modal"; -import Fade from "@mui/material/Fade"; - -import SearchOutlined from "@mui/icons-material/SearchOutlined"; -import CloseOutlined from "@mui/icons-material/CloseOutlined"; - -import { - CategoryFilter, - ProductItem, - CreateProduct, - EditProduct, -} from "../../components"; -import { IStore, IProduct, Nullable } from "../../interfaces"; - -type StoreProductsProps = { - record: IStore; - close: () => void; - visible: boolean; -}; - -export const StoreProducts: React.FC = ({ - record, - close: modalClose, - visible: modalVisible, -}) => { - const t = useTranslate(); - - const { tableQueryResult, setFilters, setCurrent, filters, pageCount } = - useTable({ - resource: "products", - initialPageSize: 12, - syncWithLocation: false, - }); - - const createDrawerFormProps = useModalForm< - IProduct, - HttpError, - Nullable - >({ - refineCoreProps: { - action: "create", - resource: "products", - redirect: false, - }, - }); - - const { - modal: { show: showCreateDrawer }, - } = createDrawerFormProps; - - const editDrawerFormProps = useModalForm< - IProduct, - HttpError, - Nullable - >({ - refineCoreProps: { - action: "edit", - resource: "products", - redirect: false, - }, - }); - - const { - modal: { show: showEditDrawer }, - } = editDrawerFormProps; - - const { data: productData } = tableQueryResult; - - const mergedData = - productData?.data.map((product) => ({ - ...record?.products.find( - (storeProduct) => storeProduct.id === product.id, - ), - ...product, - })) || []; - - const { mutate } = useUpdate(); - - const updateStock = (changedValue: number, clickedProduct: IProduct) => { - const shopProduct = record.products.find((p) => p.id === clickedProduct.id); - - if (shopProduct) { - shopProduct.stock = changedValue; - - mutate({ - id: record.id, - resource: "stores", - values: { - products: record.products, - }, - successNotification: false, - mutationMode: "optimistic", - }); - } - }; - - const style = { - width: "100%", - position: "absolute", - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - maxWidth: { xs: 380, sm: 580, md: 880, lg: 1000 }, - heigth: 650, - bgcolor: "background.paper", - p: 2, - my: 2, - borderRadius: "8px", - }; - - return ( - - - - - - - modalClose()}> - - - - - - - - {t("products.products")} - - ) => { - setFilters([ - { - field: "name", - operator: "contains", - value: e.target.value, - }, - ...filters, - ]); - }} - /> - - - - - showCreateDrawer()} - variant="outlined" - sx={{ marginBottom: "5px" }} - > - {t("stores.buttons.addProduct")} - - - - - {mergedData.length > 0 ? ( - mergedData.map((product: IProduct) => ( - - - - )) - ) : ( - - - {t("products.noProducts")} - - - )} - - , - page: number, - ) => { - event.preventDefault(); - setCurrent(page); - }} - /> - - - - - - {t("stores.tagFilterDescription")} - - - - - - - - - - ); -}; diff --git a/examples/finefoods-material-ui/src/components/store/table/index.tsx b/examples/finefoods-material-ui/src/components/store/table/index.tsx new file mode 100644 index 000000000000..25e2181f2523 --- /dev/null +++ b/examples/finefoods-material-ui/src/components/store/table/index.tsx @@ -0,0 +1,85 @@ +import { useMemo } from "react"; +import { useTranslate } from "@refinedev/core"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import { EditButton, TextFieldComponent, useDataGrid } from "@refinedev/mui"; +import Paper from "@mui/material/Paper"; +import Typography from "@mui/material/Typography"; +import { StoreStatus } from "../../../components"; +import { IStore } from "../../../interfaces"; + +export const StoreTable = () => { + const t = useTranslate(); + + const { dataGridProps } = useDataGrid({ + initialPageSize: 10, + }); + + const columns = useMemo[]>( + () => [ + { + field: "id", + headerName: "ID #", + width: 72, + renderCell: function render({ row }) { + return #{row.id}; + }, + }, + { + field: "title", + headerName: t("stores.fields.title"), + flex: 1, + minWidth: 200, + }, + { + field: "address", + headerName: t("stores.fields.address"), + flex: 2, + width: 356, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "email", + headerName: t("stores.fields.email"), + minWidth: 188, + }, + { + field: "gsm", + headerName: t("stores.fields.gsm"), + minWidth: 132, + }, + { + field: "isActive", + headerName: t("stores.fields.isActive.label"), + width: 110, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "actions", + headerName: t("table.actions"), + type: "actions", + align: "center", + headerAlign: "center", + renderCell: function render({ row }) { + return ; + }, + }, + ], + [t], + ); + + return ( + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/components/title/index.tsx b/examples/finefoods-material-ui/src/components/title/index.tsx index b43a5f34e2ec..d529ec546818 100644 --- a/examples/finefoods-material-ui/src/components/title/index.tsx +++ b/examples/finefoods-material-ui/src/components/title/index.tsx @@ -1,7 +1,7 @@ import { Link } from "react-router-dom"; import Box from "@mui/material/Box"; -import { BikeWhiteIcon, FineFoodsIcon } from "../../components/icons"; +import { FinefoodsLogoIcon, FinefoodsLogoText } from "../icons/finefoods-logo"; type TitleProps = { collapsed: boolean; @@ -11,15 +11,21 @@ export const Title: React.FC = ({ collapsed }) => { return ( - {collapsed ? : } + {collapsed ? ( + + ) : ( + <> + + + + )} ); diff --git a/examples/finefoods-material-ui/src/contexts/index.tsx b/examples/finefoods-material-ui/src/contexts/index.tsx index 3d866bcd21d0..7f9d3287b2bc 100644 --- a/examples/finefoods-material-ui/src/contexts/index.tsx +++ b/examples/finefoods-material-ui/src/contexts/index.tsx @@ -1,6 +1,7 @@ import React, { createContext, PropsWithChildren, + useContext, useEffect, useState, } from "react"; @@ -57,3 +58,13 @@ export const ColorModeContextProvider: React.FC = ({ ); }; + +export const useColorModeContext = () => { + const context = useContext(ColorModeContext); + + if (context === undefined) { + throw new Error("useColorModeContext must be used within a ConfigProvider"); + } + + return context; +}; diff --git a/examples/finefoods-material-ui/src/index.tsx b/examples/finefoods-material-ui/src/index.tsx index 87632378c097..6e578baf7bb8 100644 --- a/examples/finefoods-material-ui/src/index.tsx +++ b/examples/finefoods-material-ui/src/index.tsx @@ -14,7 +14,7 @@ const container = document.getElementById("root"); const root = createRoot(container!); root.render( - + , diff --git a/examples/finefoods-material-ui/src/interfaces/index.d.ts b/examples/finefoods-material-ui/src/interfaces/index.d.ts index ea0889b3a9a9..8a773b868809 100644 --- a/examples/finefoods-material-ui/src/interfaces/index.d.ts +++ b/examples/finefoods-material-ui/src/interfaces/index.d.ts @@ -45,16 +45,17 @@ export interface IIdentity { export interface IAddress { text: string; - coordinate: [string, string]; + coordinate: [string | number, string | number]; } export interface IFile { + lastModified?: number; name: string; - percent: number; + percent?: number; size: number; - status: "error" | "success" | "done" | "uploading" | "removed"; + status?: "error" | "success" | "done" | "uploading" | "removed"; type: string; - uid: string; + uid?: string; url: string; } @@ -74,20 +75,6 @@ export interface IStore { products: IProduct[]; } -export interface ICourier { - id: number; - name: string; - surname: string; - email: string; - gender: string; - gsm: string; - createdAt: string; - accountNumber: string; - licensePlate: string; - address: string; - avatar: IFile[]; - store: IStore; -} export interface IOrder { id: number; user: IUser; @@ -107,7 +94,7 @@ export interface IProduct { name: string; isActive: boolean; description: string; - images: IFile[]; + images: (IFile & { thumbnailUrl?: string })[]; createdAt: string; price: number; category: ICategory; @@ -134,15 +121,26 @@ export interface IUserFilterVariables { isActive: boolean | string; } +export interface ICourierStatus { + id: number; + text: "Available" | "Offline" | "On delivery"; +} + export interface ICourier { id: number; name: string; surname: string; + email: string; gender: string; gsm: string; createdAt: string; - isActive: boolean; + accountNumber: string; + licensePlate: string; + address: string; avatar: IFile[]; + store: IStore; + status: ICourierStatus; + vehicle: IVehicle; } export interface IReview { @@ -155,6 +153,21 @@ export interface IReview { comment: string[]; } +export interface ITrendingProducts { + id: number; + product: IProduct; + orderCount: number; +} + +export type IVehicle = { + model: string; + vehicleType: string; + engineSize: number; + color: string; + year: number; + id: number; +}; + export type Nullable = { [P in keyof T]: T[P] | null; }; diff --git a/examples/finefoods-material-ui/src/interfaces/theme.d.ts b/examples/finefoods-material-ui/src/interfaces/theme.d.ts deleted file mode 100644 index d62bbad0e26f..000000000000 --- a/examples/finefoods-material-ui/src/interfaces/theme.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -import "@refinedev/mui"; - -export interface CustomTheme { - timeLine: { - color: { - pending: string; - ready: string; - delivered: string; - cancelled: string; - onTheWay: string; - }; - dotColor: { - pending: string; - ready: string; - delivered: string; - cancelled: string; - onTheWay: string; - }; - }; -} - -declare module "@mui/material/styles" { - interface Theme extends import("@mui/material/styles").Theme, CustomTheme {} - interface ThemeOptions - extends import("@mui/material/styles").ThemeOptions, - CustomTheme {} -} diff --git a/examples/finefoods-material-ui/src/pages/auth/index.tsx b/examples/finefoods-material-ui/src/pages/auth/index.tsx index 80e2348ba353..86385abceeeb 100644 --- a/examples/finefoods-material-ui/src/pages/auth/index.tsx +++ b/examples/finefoods-material-ui/src/pages/auth/index.tsx @@ -1,6 +1,11 @@ import * as React from "react"; import { AuthPage as MUIAuthPage, AuthProps } from "@refinedev/mui"; import { Link } from "react-router-dom"; +import Box from "@mui/material/Box"; +import { + FinefoodsLogoIcon, + FinefoodsLogoText, +} from "../../components/icons/finefoods-logo"; const authWrapperProps = { style: { @@ -12,17 +17,30 @@ const authWrapperProps = { const renderAuthContent = (content: React.ReactNode) => { return ( -
+
- fineFoods Logo + + + + {content}
diff --git a/examples/finefoods-material-ui/src/pages/categories/list.tsx b/examples/finefoods-material-ui/src/pages/categories/list.tsx index 954e4cd9ddfd..bb19b1925ff3 100644 --- a/examples/finefoods-material-ui/src/pages/categories/list.tsx +++ b/examples/finefoods-material-ui/src/pages/categories/list.tsx @@ -1,427 +1,119 @@ -import React, { useCallback } from "react"; +import React from "react"; import { HttpError, IResourceComponentsProps, + useList, useTranslate, } from "@refinedev/core"; -import { - BooleanField, - DateField, - EditButton, - List, - NumberField, - SaveButton, - useDataGrid, -} from "@refinedev/mui"; -import { useForm, useModalForm } from "@refinedev/react-hook-form"; -import { useTable } from "@refinedev/react-table"; -import { ColumnDef, flexRender, Row } from "@tanstack/react-table"; -import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid"; - +import { useDataGrid } from "@refinedev/mui"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; import Avatar from "@mui/material/Avatar"; -import Button from "@mui/material/Button"; -import Checkbox from "@mui/material/Checkbox"; -import IconButton from "@mui/material/IconButton"; -import Stack from "@mui/material/Stack"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; -import TablePagination from "@mui/material/TablePagination"; -import TableRow from "@mui/material/TableRow"; -import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; - -import AddCircleOutline from "@mui/icons-material/AddCircleOutline"; -import Edit from "@mui/icons-material/Edit"; -import RemoveCircleOutline from "@mui/icons-material/RemoveCircleOutline"; - -import { EditProduct } from "../../components"; -import { ICategory, IProduct, Nullable } from "../../interfaces"; +import Paper from "@mui/material/Paper"; +import Box from "@mui/material/Box"; +import Skeleton from "@mui/material/Skeleton"; +import { + RefineListView, + CategoryStatus, + CustomTooltip, +} from "../../components"; +import { ICategory, IProduct } from "../../interfaces"; export const CategoryList: React.FC = () => { - const { - refineCore: { onFinish, id, setId }, - register, - handleSubmit, - } = useForm({ - refineCoreProps: { - redirect: false, - action: "edit", - }, - }); - const t = useTranslate(); - const columns = React.useMemo[]>( - () => [ - { - id: "title", - accessorKey: "title", - header: t("categories.fields.title"), - cell: function render({ row, getValue }) { - return ( - - row.toggleExpanded()}> - {row.getIsExpanded() ? ( - - ) : ( - - )} - - {getValue() as string} - - ); - }, - }, - { - id: "isActive", - header: t("categories.fields.isActive"), - accessorKey: "isActive", - cell: function render({ getValue }) { - return ; - }, - }, - { - id: "actions", - header: t("table.actions"), - accessorKey: "id", - cell: function render({ getValue }) { - return ( - - {id ? ( - <> - { - handleEditButtonClick(getValue() as string); - }} - > - Edit - -
Cancel
- - ) : ( - { - setId(getValue() as string); - }} - > - - - )} -
- ); - }, - }, - ], - [t], - ); - - const { - options: { - state: { pagination }, - pageCount, - }, - getHeaderGroups, - getRowModel, - setPageIndex, - setPageSize, - refineCore: { tableQueryResult }, - } = useTable({ - columns, - initialState: { - sorting: [{ id: "title", desc: false }], + const { dataGridProps } = useDataGrid({ + pagination: { + mode: "off", }, }); - const renderRowSubComponent = useCallback( - ({ row }: { row: Row }) => ( - - ), - [], - ); - - const handleEditButtonClick = (editId: string) => { - setId(editId); - }; - - const renderEditRow = useCallback((row: Row) => { - const { id, title, isActive } = row.original; - - return ( - - - - row.toggleExpanded()}> - {row.getIsExpanded() ? ( - - ) : ( - - )} - - - - - - - - - - {t("buttons.save")} - - - - ); - }, []); - - return ( - -
- - - - {getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ))} - - ))} - - - {getRowModel().rows.map((row) => { - return ( - - {id === (row.original as ICategory).id ? ( - renderEditRow(row) - ) : ( - - {row.getAllCells().map((cell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ); - })} - - )} - {row.getIsExpanded() ? ( - - - {renderRowSubComponent({ - row, - })} - - - ) : null} - - ); - })} - -
-
- setPageIndex(newPage)} - onRowsPerPageChange={(event: React.ChangeEvent) => { - setPageSize(parseInt(event.target.value, 10)); - setPageIndex(0); - }} - /> - -
- ); -}; - -const CategoryProductsTable: React.FC<{ record: ICategory }> = ({ record }) => { - const t = useTranslate(); - - const { dataGridProps } = useDataGrid({ + const { data: productsData, isLoading: productsIsLoading } = useList< + IProduct, + HttpError + >({ resource: "products", - initialPageSize: 5, - permanentFilter: [ - { - field: "category.id", - operator: "eq", - value: record.id, - }, - ], - syncWithLocation: false, + pagination: { + mode: "off", + }, }); + const products = productsData?.data || []; - const columns = React.useMemo[]>( + const columns = React.useMemo[]>( () => [ { - field: "image", - renderHeader: function render() { - return <>; - }, - filterable: false, - filterOperators: undefined, - disableColumnMenu: true, - hideSortIcons: true, - renderCell: function render({ row }) { - return ( - - ); - }, - flex: 1, - minWidth: 100, + field: "title", + headerName: t("categories.fields.title"), + width: 232, }, { - field: "name", - headerName: t("products.fields.name"), + field: "product", + headerName: t("categories.fields.products"), flex: 1, - minWidth: 180, - }, - { - field: "price", - headerName: t("products.fields.price"), - renderCell: function render({ value }) { + renderCell: function render({ row }) { + const categoryProducts = products.filter( + (product) => product.category.id === row.id, + ); return ( - + + {productsIsLoading && + Array.from({ length: 10 }).map((_, index) => { + return ( + + ); + })} + + {!productsIsLoading && + categoryProducts.map((product) => { + const image = product.images?.[0]; + const thumbnailUrl = image?.thumbnailUrl || image?.url; + return ( + + + + ); + })} + ); }, - flex: 1, - minWidth: 100, }, { field: "isActive", - - headerName: t("products.fields.isActive"), + headerName: t("categories.fields.isActive.label"), + width: 116, renderCell: function render({ row }) { - return ; - }, - flex: 0.5, - minWidth: 100, - }, - { - field: "createdAt", - headerName: t("products.fields.createdAt"), - renderCell: function render({ row }) { - return ; - }, - flex: 1, - minWidth: 200, - }, - - { - field: "actions", - headerName: t("table.actions"), - type: "actions", - getActions: function render({ row }) { - return [ - } - onClick={() => showEditDrawer(row.id)} - showInMenu - />, - ]; + return ; }, - flex: 0.5, - minWidth: 100, }, ], - [t], + [t, products], ); - const editDrawerFormProps = useModalForm< - IProduct, - HttpError, - Nullable - >({ - refineCoreProps: { - action: "edit", - resource: "products", - redirect: false, - }, - }); - - const { - modal: { show: showEditDrawer }, - } = editDrawerFormProps; - return ( - - - - + + + + + ); }; diff --git a/examples/finefoods-material-ui/src/pages/couriers/create.tsx b/examples/finefoods-material-ui/src/pages/couriers/create.tsx index 855947509d22..7bf5c33a5c17 100644 --- a/examples/finefoods-material-ui/src/pages/couriers/create.tsx +++ b/examples/finefoods-material-ui/src/pages/couriers/create.tsx @@ -1,569 +1,517 @@ import React from "react"; -import axios from "axios"; -import InputMask from "react-input-mask"; import { - IResourceComponentsProps, useTranslate, useApiUrl, HttpError, + useGetToPath, + useGo, } from "@refinedev/core"; -import { Create, SaveButton, useAutocomplete } from "@refinedev/mui"; -import { useStepsForm } from "@refinedev/react-hook-form"; +import InputMask from "react-input-mask"; +import { useSearchParams } from "react-router-dom"; +import { useAutocomplete } from "@refinedev/mui"; +import { + UseStepsFormReturnType, + useStepsForm, +} from "@refinedev/react-hook-form"; import { Controller } from "react-hook-form"; - -import Avatar from "@mui/material/Avatar"; import Button from "@mui/material/Button"; import Box from "@mui/material/Box"; import FormControl from "@mui/material/FormControl"; import FormHelperText from "@mui/material/FormHelperText"; -import FormLabel from "@mui/material/FormLabel"; -import Grid from "@mui/material/Grid"; import Stack from "@mui/material/Stack"; import Step from "@mui/material/Step"; import Stepper from "@mui/material/Stepper"; import StepButton from "@mui/material/StepButton"; import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; import Autocomplete from "@mui/material/Autocomplete"; -import Input from "@mui/material/Input"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import IconButton from "@mui/material/IconButton"; +import CloseIcon from "@mui/icons-material/Close"; import type { TextFieldProps } from "@mui/material/TextField"; - import { ICourier, IFile, IStore, Nullable } from "../../interfaces"; +import { CourierImageUpload } from "../../components"; +import { useImageUpload } from "../../utils"; +import Divider from "@mui/material/Divider"; -export const CourierCreate: React.FC = () => { +export const CourierCreate = () => { + const getToPath = useGetToPath(); + const [searchParams] = useSearchParams(); + const go = useGo(); const t = useTranslate(); - const stepTitles = [ - t("couriers.steps.content"), - t("couriers.steps.relations"), - ]; - const apiUrl = useApiUrl(); - const { - refineCore: { onFinish, formLoading }, - control, - watch, - register, - handleSubmit, - setValue, - formState: { errors }, - steps: { currentStep, gotoStep }, - } = useStepsForm>({ + const stepsForm = useStepsForm>({ stepsProps: { isBackValidate: false, }, - warnWhenUnsavedChanges: true, }); - const imageInput = watch("avatar"); - - const onChangeHandler = async ( - event: React.ChangeEvent, - ) => { - const formData = new FormData(); + const { stepTitles, stepFormFields } = useStepsFormList({ stepsForm }); - const target = event.target; - const file: File = (target.files as FileList)[0]; + const isLastStep = stepsForm.steps.currentStep === stepFormFields.length - 1; - formData.append("file", file); + const isFirstStep = stepsForm.steps.currentStep === 0; - const res = await axios.post<{ url: string }>( - `${apiUrl}/media/upload`, - formData, - { - withCredentials: false, - headers: { - "Access-Control-Allow-Origin": "*", - }, + const onModalClose = () => { + go({ + to: + searchParams.get("to") ?? + getToPath({ + action: "list", + }) ?? + "", + query: { + to: undefined, }, - ); - - const { name, size, type, lastModified } = file; - - // eslint-disable-next-line - const imagePaylod: any = [ - { - name, - size, - type, - lastModified, - url: res.data.url, + options: { + keepQuery: true, }, - ]; - setValue("avatar", imagePaylod, { - shouldDirty: true, + type: "replace", }); }; - const { autocompleteProps } = useAutocomplete({ - resource: "stores", - }); - - const renderFormByStep = (step: number) => { - switch (step) { - case 0: - return ( - <> - - - - - - {t("couriers.fields.images.description")} - - - {t("couriers.fields.images.validation")} - - - - - - - - - - {t("couriers.fields.name")} - - - {errors.name && ( - - {errors.name.message} - - )} - - - - {t("couriers.fields.surname")} - - - {errors.surname && ( - - {errors.surname.message} - - )} - - - - {t("couriers.fields.gender.label")} - - ( - { - field.onChange(value); - }} - options={["Male", "Female"]} - renderInput={(params) => ( - - )} - /> - )} - /> - {errors.gender && ( - - {errors.gender.message} - - )} - - - - - - - - {t("stores.fields.gsm")} - - - {/* @ts-expect-error False alarm */} - {(props: TextFieldProps) => ( - - )} - - {errors.gsm && ( - - {errors.gsm.message} - - )} - - - - {t("couriers.fields.email")} - - - {errors.email && ( - - {errors.email.message} - - )} - - - - - - - - {t("stores.fields.address")} - - - {errors.address && ( - - {errors.address.message} - - )} - - - - - - ); - case 1: - return ( - <> - - - - - - {t("couriers.fields.store")} - - ( - { - field.onChange(value); - }} - getOptionLabel={(item) => { - return item.title ? item.title : ""; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} - /> - )} - /> - {errors.store && ( - - {errors.store.message} - - )} - - - - - - {t("couriers.fields.vehicle")} - - - {errors.licensePlate && ( - - {errors.licensePlate.message} - - )} - - - - - - - {t("couriers.fields.accountNumber")} - - - {errors.accountNumber && ( - - {errors.accountNumber.message} - - )} - - - - - ); - } - }; - return ( - - {currentStep > 0 && ( - - )} - {currentStep < stepTitles.length - 1 && ( - - )} - {currentStep === stepTitles.length - 1 && ( - - )} - - } + - {t("couriers.actions.add")} + theme.palette.grey[500], }} - autoComplete="off" > - + + + + {stepTitles.map((label, index) => ( + theme.palette.mode === "light" ? "white" : "black", }, }} > - gotoStep(index)}>{label} + stepsForm.steps.gotoStep(index)}> + {label} + ))} -
- {renderFormByStep(currentStep)} -
-
+ +
{stepFormFields[stepsForm.steps.currentStep]}
+
+ + + + + + {!isFirstStep && ( + + )} + {isLastStep ? ( + + ) : ( + + )} + + + + ); }; + +type UseStepsFormList = { + stepsForm: UseStepsFormReturnType>; +}; + +const useStepsFormList = ({ stepsForm }: UseStepsFormList) => { + const t = useTranslate(); + const apiUrl = useApiUrl(); + + const { + watch, + control, + setValue, + formState: { errors }, + refineCore: { formLoading }, + } = stepsForm; + const avatarInput: IFile[] | null = watch("avatar"); + + const { autocompleteProps: storesAutoCompleteProps } = + useAutocomplete({ + resource: "stores", + }); + + const { autocompleteProps: vehiclesAutoCompleteProps } = useAutocomplete({ + resource: "vehicles", + }); + + const imageUploadOnChangeHandler = async ( + event: React.ChangeEvent, + ) => { + const target = event.target; + const file: File = (target.files as FileList)[0]; + + const image = await useImageUpload({ + apiUrl, + file, + }); + + setValue("avatar", image, { shouldValidate: true }); + }; + + const stepPersonal = ( + + { + return ( + + ); + }} + /> + {errors.avatar && ( + {errors.avatar.message} + )} + + { + return ( + + palette.mode === "dark" + ? "#1E1E1E" + : palette.background.paper, + }, + }} + label={t("couriers.fields.name.label")} + placeholder={t("couriers.fields.name.label")} + /> + ); + }} + /> + {errors.name && ( + {errors.name.message} + )} + {" "} + + { + return ( + + ); + }} + /> + {errors.email && ( + {errors.email.message} + )} + + + { + return ( + + {/* @ts-expect-error False alarm */} + {(props: TextFieldProps) => ( + + )} + + ); + }} + /> + {errors.gsm && ( + {errors.gsm.message} + )} + + + { + return ( + + ); + }} + /> + {errors.address && ( + {errors.address.message} + )} + + + ); + + const stepCompany = ( + + + ( + { + field.onChange(value); + }} + getOptionLabel={(item) => { + return item.title ? item.title : ""; + }} + isOptionEqualToValue={(option, value) => + value === undefined || + option?.id?.toString() === (value?.id ?? value)?.toString() + } + renderInput={(params) => ( + + )} + /> + )} + /> + {errors.store && ( + {errors.store.message} + )} + + + { + console.log("accountNumber", field); + return ( + + ); + }} + /> + {errors.accountNumber && ( + {errors.accountNumber.message} + )} + + + ); + const stepVehicle = ( + + + ( + { + field.onChange(value); + }} + getOptionLabel={(item) => { + return item.model ? item.model : ""; + }} + isOptionEqualToValue={(option, value) => + value === undefined || + option?.id?.toString() === (value?.id ?? value)?.toString() + } + renderInput={(params) => ( + + )} + /> + )} + /> + {errors.vehicle && ( + {errors.vehicle.message} + )} + + + { + console.log("licensePlate", field); + return ( + + ); + }} + /> + {errors.licensePlate && ( + {errors.licensePlate.message} + )} + + + ); + + const stepTitles = [ + t("couriers.steps.personal"), + t("couriers.steps.company"), + t("couriers.steps.vehicle"), + ]; + + return { + stepTitles, + stepFormFields: [stepPersonal, stepCompany, stepVehicle], + }; +}; diff --git a/examples/finefoods-material-ui/src/pages/couriers/edit.tsx b/examples/finefoods-material-ui/src/pages/couriers/edit.tsx index 5b17feffbc56..140e110442b3 100644 --- a/examples/finefoods-material-ui/src/pages/couriers/edit.tsx +++ b/examples/finefoods-material-ui/src/pages/couriers/edit.tsx @@ -1,518 +1,287 @@ import React from "react"; -import axios from "axios"; import InputMask from "react-input-mask"; import { - IResourceComponentsProps, useTranslate, - useApiUrl, HttpError, + useApiUrl, + useNavigation, } from "@refinedev/core"; -import { Edit, SaveButton, useAutocomplete } from "@refinedev/mui"; -import { useStepsForm } from "@refinedev/react-hook-form"; +import { DeleteButton, ListButton, useAutocomplete } from "@refinedev/mui"; +import { useForm } from "@refinedev/react-hook-form"; import { Controller } from "react-hook-form"; - -import Avatar from "@mui/material/Avatar"; import Button from "@mui/material/Button"; -import Box from "@mui/material/Box"; import FormControl from "@mui/material/FormControl"; import FormHelperText from "@mui/material/FormHelperText"; -import FormLabel from "@mui/material/FormLabel"; -import Grid from "@mui/material/Grid"; +import Grid from "@mui/material/Unstable_Grid2"; import Stack from "@mui/material/Stack"; -import Step from "@mui/material/Step"; -import Stepper from "@mui/material/Stepper"; -import StepButton from "@mui/material/StepButton"; import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; import Autocomplete from "@mui/material/Autocomplete"; -import Input from "@mui/material/Input"; +import Paper from "@mui/material/Paper"; import type { TextFieldProps } from "@mui/material/TextField"; +import Box from "@mui/material/Box"; +import Divider from "@mui/material/Divider"; +import ArrowBack from "@mui/icons-material/ArrowBack"; +import { useTheme } from "@mui/material/styles"; +import { ICourier, IFile, IStore, Nullable } from "../../interfaces"; +import { CourierImageUpload, CourierTableReviews } from "../../components"; +import { useImageUpload } from "../../utils"; -import { ICourier, IStore, Nullable } from "../../interfaces"; - -export const CourierEdit: React.FC = () => { +export const CourierEdit = () => { + const { list } = useNavigation(); const t = useTranslate(); - const stepTitles = [ - t("couriers.steps.content"), - t("couriers.steps.relations"), - ]; const apiUrl = useApiUrl(); + const { palette } = useTheme(); const { - refineCore: { onFinish, formLoading }, - control, watch, - register, - handleSubmit, + control, setValue, formState: { errors }, - steps: { currentStep, gotoStep }, - } = useStepsForm< - ICourier, - HttpError, - Nullable< - ICourier & { - avatar: any; // eslint-disable-line - } - > - >({ - stepsProps: { - isBackValidate: false, + refineCore: { formLoading, queryResult }, + saveButtonProps, + } = useForm>(); + const courier = queryResult?.data?.data; + const avatarInput: IFile[] | null = watch("avatar"); + + const { autocompleteProps: storesAutoCompleteProps } = + useAutocomplete({ + resource: "stores", + queryOptions: { + enabled: !!courier, + }, + }); + + const { autocompleteProps: vehiclesAutoCompleteProps } = useAutocomplete({ + resource: "vehicles", + queryOptions: { + enabled: !!courier, }, }); - const imageInput = watch("avatar"); - - const onChangeHandler = async ( + const imageUploadOnChangeHandler = async ( event: React.ChangeEvent, ) => { - const formData = new FormData(); - const target = event.target; const file: File = (target.files as FileList)[0]; - formData.append("file", file); - - const res = await axios.post<{ url: string }>( - `${apiUrl}/media/upload`, - formData, - { - withCredentials: false, - headers: { - "Access-Control-Allow-Origin": "*", - }, - }, - ); - - const { name, size, type, lastModified } = file; - - const imagePayload = [ - { - name, - size, - type, - lastModified, - url: res.data.url, - }, - ]; - - setValue("avatar", imagePayload, { - shouldDirty: true, + const image = await useImageUpload({ + apiUrl, + file, }); - }; - const { autocompleteProps } = useAutocomplete({ - resource: "stores", - }); + setValue("avatar", image, { shouldValidate: true }); + }; - const renderFormByStep = (step: number) => { - switch (step) { - case 0: - return ( - <> - + } + /> + + + +
+ - - - - - {t("couriers.fields.images.description")} - - - {t("couriers.fields.images.validation")} - - - - - - - - - - {t("couriers.fields.name")} - - - {errors.name && ( - - {errors.name.message} - - )} - - - - {t("couriers.fields.surname")} - - - {errors.surname && ( - - {errors.surname.message} - - )} - - - - {t("couriers.fields.gender.label")} - - ( - { - field.onChange(value); - }} - options={["Male", "Female"]} - renderInput={(params) => ( - - )} - /> - )} - /> - {errors.gender && ( - - {errors.gender.message} - - )} - - - - + {errors.avatar && ( + {errors.avatar.message} + )} + + { + return ( + + ); + }} + /> + {errors.name && ( + {errors.name.message} + )} + + + + + + - - - - {t("stores.fields.gsm")} - + render={({ field }) => { + return ( {/* @ts-expect-error False alarm */} {(props: TextFieldProps) => ( )} - {errors.gsm && ( - - {errors.gsm.message} - - )} - - - - {t("couriers.fields.email")} - + ); + }} + /> + {errors.gsm && ( + {errors.gsm.message} + )} + {" "} + + { + return ( - {errors.email && ( - - {errors.email.message} - - )} - - - - - - - - {t("stores.fields.address")} - - - {errors.address && ( - - {errors.address.message} - - )} - - - - - - ); - case 1: - return ( - <> - - - - - - {t("couriers.fields.store")} - - ( - + {errors.address && ( + + {errors.address.message} + + )} + + + { + return ( + { - field.onChange(value); - }} - getOptionLabel={(item) => { - return item.title ? item.title : ""; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} + variant="outlined" + type="email" + id="email" + required + label={t("couriers.fields.email.label")} + placeholder={t("couriers.fields.email.label")} /> - )} - /> - {errors.store && ( - - {errors.store.message} - - )} - - - - - - {t("couriers.fields.vehicle")} - - - {errors.licensePlate && ( - - {errors.licensePlate.message} - - )} - - - - - - - {t("couriers.fields.accountNumber")} - - + {errors.email && ( + + {errors.email.message} + + )} + + + { + return ( + + ); + }} /> {errors.accountNumber && ( @@ -520,64 +289,141 @@ export const CourierEdit: React.FC = () => { )} - - - - ); - } - }; - - return ( - - {currentStep > 0 && ( - - )} - {currentStep < stepTitles.length - 1 && ( - - )} - {currentStep === stepTitles.length - 1 && ( - - )} - - } - > - - - {stepTitles.map((label, index) => ( - - gotoStep(index)}>{label} - - ))} - -
- {renderFormByStep(currentStep)} -
-
+ + { + return ( + + ); + }} + /> + {errors.licensePlate && ( + + {errors.licensePlate.message} + + )} + + + ( + { + field.onChange(value); + }} + getOptionLabel={(item) => { + return item.model ? item.model : ""; + }} + isOptionEqualToValue={(option, value) => + value === undefined || + option?.id?.toString() === + (value?.id ?? value)?.toString() + } + renderInput={(params) => ( + + )} + /> + )} + /> + {errors.vehicle && ( + + {errors.vehicle.message} + + )} + + + ( + { + field.onChange(value); + }} + getOptionLabel={(item) => { + return item.title ? item.title : ""; + }} + isOptionEqualToValue={(option, value) => + value === undefined || + option?.id?.toString() === + (value?.id ?? value)?.toString() + } + renderInput={(params) => ( + + )} + /> + )} + /> + {errors.store && ( + + {errors.store.message} + + )} + + + + { + list("couriers"); + }} + /> + + +
+
+
+
+ + + +
+ ); }; diff --git a/examples/finefoods-material-ui/src/pages/couriers/index.ts b/examples/finefoods-material-ui/src/pages/couriers/index.ts index 817d7f6b8a12..b9af745e6bcf 100644 --- a/examples/finefoods-material-ui/src/pages/couriers/index.ts +++ b/examples/finefoods-material-ui/src/pages/couriers/index.ts @@ -1,4 +1,3 @@ export * from "./list"; -export * from "./show"; export * from "./create"; export * from "./edit"; diff --git a/examples/finefoods-material-ui/src/pages/couriers/list.tsx b/examples/finefoods-material-ui/src/pages/couriers/list.tsx index b5bcd28fdf5b..26eaf0ec3b5c 100644 --- a/examples/finefoods-material-ui/src/pages/couriers/list.tsx +++ b/examples/finefoods-material-ui/src/pages/couriers/list.tsx @@ -1,119 +1,97 @@ -import React from "react"; - -import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid"; -import { - IResourceComponentsProps, - useDelete, - useNavigation, - useTranslate, -} from "@refinedev/core"; -import { List, useDataGrid } from "@refinedev/mui"; +import { PropsWithChildren, useMemo } from "react"; +import { useGo, useNavigation, useTranslate } from "@refinedev/core"; +import { EditButton, useDataGrid } from "@refinedev/mui"; +import { useLocation } from "react-router-dom"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import Paper from "@mui/material/Paper"; import Avatar from "@mui/material/Avatar"; -import Stack from "@mui/material/Stack"; -import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; - -import Close from "@mui/icons-material/Close"; -import Edit from "@mui/icons-material/Edit"; - +import Button from "@mui/material/Button"; +import { CourierRating, CourierStatus, RefineListView } from "../../components"; import { ICourier } from "../../interfaces"; -export const CourierList: React.FC = () => { - const { show, edit } = useNavigation(); +export const CourierList = ({ children }: PropsWithChildren) => { + const go = useGo(); + const { pathname } = useLocation(); + const { createUrl } = useNavigation(); const t = useTranslate(); - const { mutate: mutateDelete } = useDelete(); const { dataGridProps } = useDataGrid({ - initialPageSize: 10, - initialSorter: [ - { - field: "id", - order: "desc", - }, - ], + pagination: { + pageSize: 10, + }, }); - const columns = React.useMemo[]>( + const columns = useMemo[]>( () => [ { - field: "name", - headerName: t("couriers.fields.name"), + field: "id", + headerName: "ID #", + width: 64, + renderCell: function render({ row }) { + return #{row.id}; + }, + }, + { + field: "avatar", + headerName: t("couriers.fields.avatar.label"), + width: 64, renderCell: function render({ row }) { return ( - - - - {row.name} {row.surname} - - + ); }, - flex: 1, - minWidth: 200, + }, + { + field: "name", + width: 188, + headerName: t("couriers.fields.name.label"), + }, + { + field: "licensePlate", + width: 112, + headerName: t("couriers.fields.licensePlate.label"), }, { field: "gsm", - headerName: t("couriers.fields.gsm"), - flex: 1, - minWidth: 200, + width: 132, + headerName: t("couriers.fields.gsm.label"), }, { - field: "email", - headerName: t("couriers.fields.email"), + field: "store", + minWidth: 156, flex: 1, - minWidth: 300, + headerName: t("couriers.fields.store.label"), + renderCell: function render({ row }) { + return {row.store?.title}; + }, }, { - field: "address", - headerName: t("couriers.fields.address"), + field: "rating", + width: 156, + headerName: t("couriers.fields.rating.label"), renderCell: function render({ row }) { - return ( - - - {row.address} - - - ); + return ; + }, + }, + { + field: "status", + width: 156, + headerName: t("couriers.fields.status.label"), + renderCell: function render({ row }) { + return ; }, - flex: 1, - minWidth: 300, }, { field: "actions", headerName: t("table.actions"), type: "actions", - getActions: function render({ row }) { - return [ - } - onClick={() => edit("couriers", row.id)} - showInMenu - />, - } - onClick={() => { - mutateDelete({ - resource: "couriers", - id: row.id, - mutationMode: "undoable", - }); - }} - showInMenu - />, - ]; + renderCell: function render({ row }) { + return ; }, }, ], @@ -121,22 +99,42 @@ export const CourierList: React.FC = () => { ); return ( - - { - show("couriers", row.id); - }} - /> - + <> + [ + , + ]} + > + + + + + {children} + ); }; diff --git a/examples/finefoods-material-ui/src/pages/couriers/show.tsx b/examples/finefoods-material-ui/src/pages/couriers/show.tsx deleted file mode 100644 index d6daff318152..000000000000 --- a/examples/finefoods-material-ui/src/pages/couriers/show.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import React from "react"; -import { - HttpError, - IResourceComponentsProps, - useNavigation, - useShow, - useTranslate, -} from "@refinedev/core"; -import { DataGrid, GridColDef } from "@mui/x-data-grid"; -import { List, useDataGrid } from "@refinedev/mui"; - -import Avatar from "@mui/material/Avatar"; -import Button from "@mui/material/Button"; -import Grid from "@mui/material/Grid"; -import Paper from "@mui/material/Paper"; -import Rating from "@mui/material/Rating"; -import Stack from "@mui/material/Stack"; -import Tooltip from "@mui/material/Tooltip"; -import Typography from "@mui/material/Typography"; - -import AccountBalanceOutlined from "@mui/icons-material/AccountBalanceOutlined"; -import DirectionsCarFilledOutlined from "@mui/icons-material/DirectionsCarFilledOutlined"; -import EmailOutlined from "@mui/icons-material/EmailOutlined"; -import LocalPhoneOutlined from "@mui/icons-material/LocalPhoneOutlined"; -import MapOutlined from "@mui/icons-material/MapOutlined"; -import StoreOutlined from "@mui/icons-material/StoreOutlined"; - -import { ICourier, IReview } from "../../interfaces"; - -type CourierInfoTextProps = { - icon: React.ReactNode; - text?: string; -}; - -const CourierInfoText: React.FC = ({ icon, text }) => ( - - {icon} - {text} - -); - -export const CourierShow: React.FC = () => { - const t = useTranslate(); - const { show } = useNavigation(); - - const { - queryResult: { data }, - } = useShow(); - const courier = data?.data; - - const { dataGridProps } = useDataGrid({ - resource: "reviews", - initialSorter: [ - { - field: "id", - order: "desc", - }, - ], - permanentFilter: [ - { - field: "order.courier.id", - operator: "eq", - value: courier?.id, - }, - ], - initialPageSize: 4, - queryOptions: { - enabled: courier !== undefined, - }, - syncWithLocation: false, - }); - - const columns = React.useMemo[]>( - () => [ - { - field: "order.id", - headerName: t("reviews.fields.orderId"), - renderCell: function render({ row }) { - return ( - - ); - }, - width: 150, - }, - - { - field: "review", - headerName: t("reviews.fields.review"), - renderCell: function render({ row }) { - return ( - - - {row.comment[0]} - - - ); - }, - flex: 1, - }, - { - field: "star", - headerName: t("reviews.fields.rating"), - headerAlign: "center", - flex: 1, - align: "center", - renderCell: function render({ row }) { - return ( - - - {row.star} - - - - ); - }, - }, - ], - [t], - ); - - return ( - - - - - - - {courier?.name} {courier?.surname} - - -
- - } - text={courier?.store.title} - /> - } - text={courier?.gsm} - /> - } text={courier?.email} /> - } - text={courier?.accountNumber} - /> - } text={courier?.address} /> - } - text={courier?.licensePlate} - /> - -
-
- - - - - - - -
- ); -}; diff --git a/examples/finefoods-material-ui/src/pages/users/index.ts b/examples/finefoods-material-ui/src/pages/customers/index.ts similarity index 100% rename from examples/finefoods-material-ui/src/pages/users/index.ts rename to examples/finefoods-material-ui/src/pages/customers/index.ts diff --git a/examples/finefoods-material-ui/src/pages/customers/list.tsx b/examples/finefoods-material-ui/src/pages/customers/list.tsx new file mode 100644 index 000000000000..31c7cdfd4b56 --- /dev/null +++ b/examples/finefoods-material-ui/src/pages/customers/list.tsx @@ -0,0 +1,195 @@ +import React, { PropsWithChildren } from "react"; +import { + HttpError, + useExport, + useGo, + useNavigation, + useTranslate, +} from "@refinedev/core"; +import { useLocation } from "react-router-dom"; +import { DateField, ExportButton, useDataGrid } from "@refinedev/mui"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import Avatar from "@mui/material/Avatar"; +import Typography from "@mui/material/Typography"; +import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined"; +import IconButton from "@mui/material/IconButton"; +import Paper from "@mui/material/Paper"; +import { IUser, IUserFilterVariables } from "../../interfaces"; +import { CustomTooltip, RefineListView } from "../../components"; +import { CustomerStatus } from "../../components/customer"; + +export const CustomerList = ({ children }: PropsWithChildren) => { + const go = useGo(); + const { pathname } = useLocation(); + const { showUrl } = useNavigation(); + const t = useTranslate(); + + const { dataGridProps, filters, sorters } = useDataGrid< + IUser, + HttpError, + IUserFilterVariables + >({ + initialPageSize: 10, + }); + + const columns = React.useMemo[]>( + () => [ + { + field: "orderNumber", + headerName: "ID #", + description: "ID #", + width: 52, + renderCell: function render({ row }) { + return #{row.id}; + }, + }, + { + field: "avatar", + headerName: t("users.fields.avatar.label"), + renderCell: function render({ row }) { + return ( + + ); + }, + width: 64, + align: "center", + headerAlign: "center", + sortable: false, + }, + { + field: "gsm", + headerName: t("users.fields.gsm"), + width: 120, + sortable: false, + }, + { + field: "fullName", + headerName: t("users.fields.name"), + minWidth: 140, + }, + { + field: "address", + headerName: t("users.addresses.address"), + minWidth: 284, + flex: 1, + renderCell: function render({ row }) { + const text = row.addresses[0].text; + + return ( + + + {row.addresses[0].text} + + + ); + }, + }, + { + field: "createdAt", + width: 220, + headerName: t("users.fields.createdAt"), + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "isActive", + headerName: t("users.fields.isActive.label"), + width: 120, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "actions", + headerName: t("table.actions"), + width: 80, + align: "center", + headerAlign: "center", + renderCell: function render({ row }) { + return ( + { + return go({ + to: `${showUrl("users", row.id)}`, + query: { + to: pathname, + }, + options: { + keepQuery: true, + }, + type: "replace", + }); + }} + > + + + ); + }, + }, + ], + [t], + ); + + const { isLoading, triggerExport } = useExport({ + sorters, + filters, + pageSize: 50, + maxItemCount: 50, + mapData: (item) => { + return { + id: item.id, + fullName: item.fullName, + gsm: item.gsm, + isActive: item.isActive, + createdAt: item.createdAt, + }; + }, + }); + + return ( + <> + + } + > + + + + + {children} + + ); +}; diff --git a/examples/finefoods-material-ui/src/pages/customers/show.tsx b/examples/finefoods-material-ui/src/pages/customers/show.tsx new file mode 100644 index 000000000000..76cd921bc34a --- /dev/null +++ b/examples/finefoods-material-ui/src/pages/customers/show.tsx @@ -0,0 +1,259 @@ +import React from "react"; +import { + HttpError, + IResourceComponentsProps, + useGetToPath, + useGo, + useShow, + useTranslate, +} from "@refinedev/core"; +import { useSearchParams } from "react-router-dom"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import { DateField, NumberField, useDataGrid } from "@refinedev/mui"; +import Avatar from "@mui/material/Avatar"; +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; +import LocalPhoneOutlinedIcon from "@mui/icons-material/LocalPhoneOutlined"; +import ArrowCircleRightOutlinedIcon from "@mui/icons-material/ArrowCircleRightOutlined"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import PlaceOutlinedIcon from "@mui/icons-material/PlaceOutlined"; +import { IOrder, IOrderFilterVariables, IUser } from "../../interfaces"; +import { + OrderStatus, + CustomerStatus, + Drawer, + DrawerHeader, + OrderTableColumnProducts, +} from "../../components"; + +export const CustomerShow: React.FC = () => { + const getToPath = useGetToPath(); + const [searchParams] = useSearchParams(); + const go = useGo(); + const t = useTranslate(); + + const { queryResult } = useShow(); + const user = queryResult.data?.data; + + const { dataGridProps } = useDataGrid< + IOrder, + HttpError, + IOrderFilterVariables + >({ + resource: "orders", + sorters: { + initial: [ + { + field: "createdAt", + order: "desc", + }, + ], + }, + filters: { + permanent: [ + { + field: "user.id", + operator: "eq", + value: user?.id, + }, + ], + }, + pagination: { + mode: "off", + }, + queryOptions: { + enabled: user !== undefined, + }, + syncWithLocation: false, + }); + + const columns = React.useMemo[]>( + () => [ + { + field: "orderNumber", + headerName: t("orders.fields.orderNumber"), + width: 88, + renderCell: function render({ row }) { + return #{row.id}; + }, + }, + { + field: "status.text", + headerName: t("orders.fields.status"), + width: 124, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "products", + headerName: t("orders.fields.products"), + width: 184, + sortable: false, + renderCell: function render({ row }) { + return ; + }, + }, + { + field: "amount", + align: "right", + headerAlign: "right", + headerName: t("orders.fields.amount"), + renderCell: function render({ row }) { + return ( + + ); + }, + width: 100, + }, + { + field: "store", + headerName: t("orders.fields.store"), + width: 150, + valueGetter: ({ row }) => row.store.title, + sortable: false, + }, + ], + [t], + ); + + const onDrawerCLose = () => { + go({ + to: + searchParams.get("to") ?? + getToPath({ + action: "list", + }) ?? + "", + query: { + to: undefined, + }, + options: { + keepQuery: true, + }, + type: "replace", + }); + }; + + return ( + + + + + + + + #{user?.id} + + {user?.fullName} + + + + + + + + {t("users.fields.gsm")} + + {user?.gsm} + + + + + + {t("users.fields.addresses")} + + + {user?.addresses.map((address, index) => { + const isFirst = index === 0; + + const icon = isFirst ? ( + + ) : ( + + ); + return ( + + {icon} + {address.text} + + ); + })} + + + + + + + {t("users.fields.isActive.label")} + + + + + + + + {t("users.fields.createdAt")} + + + + + + + + + + + ); +}; diff --git a/examples/finefoods-material-ui/src/pages/dashboard/index.tsx b/examples/finefoods-material-ui/src/pages/dashboard/index.tsx index f91d2026b57a..dd86d455a070 100644 --- a/examples/finefoods-material-ui/src/pages/dashboard/index.tsx +++ b/examples/finefoods-material-ui/src/pages/dashboard/index.tsx @@ -1,9 +1,16 @@ -import React from "react"; -import Card from "@mui/material/Card"; -import CardHeader from "@mui/material/CardHeader"; +import React, { useMemo, useState } from "react"; +import { useApiUrl, useCustom, useTranslate } from "@refinedev/core"; +import dayjs from "dayjs"; import Grid from "@mui/material/Grid"; -import { useTranslate } from "@refinedev/core"; - +import { NumberField } from "@refinedev/mui"; +import MonetizationOnOutlinedIcon from "@mui/icons-material/MonetizationOnOutlined"; +import ShoppingBagOutlinedIcon from "@mui/icons-material/ShoppingBagOutlined"; +import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined"; +import PlaceOutlinedIcon from "@mui/icons-material/PlaceOutlined"; +import WatchLaterOutlinedIcon from "@mui/icons-material/WatchLaterOutlined"; +import TrendingUpIcon from "@mui/icons-material/TrendingUp"; +import Select from "@mui/material/Select"; +import MenuItem from "@mui/material/MenuItem"; import { DailyOrders, DailyRevenue, @@ -13,71 +20,317 @@ import { RecentOrders, TrendingMenu, } from "../../components/dashboard"; +import { TrendIcon } from "../../components/icons"; +import { Card, RefineListView } from "../../components"; +import { IOrderChart, ISalesChart } from "../../interfaces"; + +type DateFilter = "lastWeek" | "lastMonth"; + +const DATE_FILTERS: Record< + DateFilter, + { + text: string; + value: DateFilter; + } +> = { + lastWeek: { + text: "lastWeek", + value: "lastWeek", + }, + lastMonth: { + text: "lastMonth", + value: "lastMonth", + }, +}; export const DashboardPage: React.FC = () => { const t = useTranslate(); + const API_URL = useApiUrl(); + + const [selecetedDateFilter, setSelectedDateFilter] = useState( + DATE_FILTERS.lastWeek.value, + ); + + const dateFilterQuery = useMemo(() => { + const now = dayjs(); + switch (selecetedDateFilter) { + case "lastWeek": + return { + start: now.subtract(6, "days").startOf("day").format(), + end: now.endOf("day").format(), + }; + case "lastMonth": + return { + start: now.subtract(1, "month").startOf("day").format(), + end: now.endOf("day").format(), + }; + default: + return { + start: now.subtract(7, "days").startOf("day").format(), + end: now.endOf("day").format(), + }; + } + }, [selecetedDateFilter]); + + const { data: dailyRevenueData } = useCustom<{ + data: ISalesChart[]; + total: number; + trend: number; + }>({ + url: `${API_URL}/dailyRevenue`, + method: "get", + config: { + query: dateFilterQuery, + }, + }); + const dailyRevenue = dailyRevenueData?.data; + + const { data: dailyOrdersData } = useCustom<{ + data: IOrderChart[]; + total: number; + trend: number; + }>({ + url: `${API_URL}/dailyOrders`, + method: "get", + config: { + query: dateFilterQuery, + }, + }); + const dailyOrders = dailyOrdersData?.data; + + const { data: newCustomersData } = useCustom<{ + data: ISalesChart[]; + total: number; + trend: number; + }>({ + url: `${API_URL}/newCustomers`, + method: "get", + config: { + query: dateFilterQuery, + }, + }); + const newCustomers = newCustomersData?.data; return ( - - - - - - - - - - - - - - - - - - ( + + )} + > + + + } + sx={{ + ".MuiCardContent-root:last-child": { + paddingBottom: "24px", + }, + }} + cardContentProps={{ + sx: { + height: "208px", + }, + }} + cardHeaderProps={{ + action: ( + + } + /> + ), + }} + > + + + + - } sx={{ - paddingX: { xs: 4 }, + ".MuiCardContent-root:last-child": { + paddingBottom: "24px", + }, }} + cardContentProps={{ + sx: { + height: "208px", + }, + }} + cardHeaderProps={{ + action: ( + } + /> + ), + }} + > + + + + + } + sx={{ + ".MuiCardContent-root:last-child": { + paddingBottom: "24px", + }, + }} + cardContentProps={{ + sx: { + height: "208px", + }, + }} + cardHeaderProps={{ + action: ( + } + /> + ), + }} + > + + + + + } title={t("dashboard.deliveryMap.title")} - /> - - - - - + + + + - - - - - - - - - - - - } + title={t("dashboard.timeline.title")} + > + + + + + } + title={t("dashboard.recentOrders.title")} + cardContentProps={{ + sx: { + height: "688px", + }, + }} + > + + + + - - - + } + title={t("dashboard.trendingProducts.title")} + > + + + -
+ ); }; diff --git a/examples/finefoods-material-ui/src/pages/orders/list.tsx b/examples/finefoods-material-ui/src/pages/orders/list.tsx index 37d078f3c027..1c45743126a1 100644 --- a/examples/finefoods-material-ui/src/pages/orders/list.tsx +++ b/examples/finefoods-material-ui/src/pages/orders/list.tsx @@ -1,8 +1,5 @@ import React from "react"; import { - BaseRecord, - CrudFilters, - getDefaultFilter, HttpError, IResourceComponentsProps, useExport, @@ -13,101 +10,63 @@ import { import { DateField, ExportButton, - List, NumberField, - useAutocomplete, useDataGrid, } from "@refinedev/mui"; import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid"; -import { useForm } from "@refinedev/react-hook-form"; -import { Controller } from "react-hook-form"; - -import Autocomplete from "@mui/material/Autocomplete"; -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import Card from "@mui/material/Card"; -import CardContent from "@mui/material/CardContent"; -import CardHeader from "@mui/material/CardHeader"; -import Grid from "@mui/material/Grid"; -import Stack from "@mui/material/Stack"; -import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; - import CheckOutlinedIcon from "@mui/icons-material/CheckOutlined"; import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined"; - -import { CustomTooltip, OrderStatus } from "../../components"; +import Paper from "@mui/material/Paper"; +import { OrderStatus, OrderTableColumnProducts } from "../../components/order"; import { IOrder, IOrderFilterVariables } from "../../interfaces"; +import { RefineListView } from "../../components"; export const OrderList: React.FC = () => { const t = useTranslate(); const { mutate } = useUpdate(); - const { dataGridProps, search, filters, sorter } = useDataGrid< + const { dataGridProps, filters, sorters } = useDataGrid< IOrder, HttpError, IOrderFilterVariables >({ initialPageSize: 10, - onSearch: (params) => { - const filters: CrudFilters = []; - const { q, store, user, status } = params; - - filters.push({ - field: "q", - operator: "eq", - value: q !== "" ? q : undefined, - }); - - filters.push({ - field: "store.id", - operator: "eq", - value: (store ?? [].length) > 0 ? store : undefined, - }); - - filters.push({ - field: "user.id", - operator: "eq", - value: user, - }); - - filters.push({ - field: "status.text", - operator: "in", - value: (status ?? []).length > 0 ? status : undefined, - }); - - return filters; - }, }); const columns = React.useMemo[]>( () => [ { field: "orderNumber", - headerName: t("orders.fields.orderNumber"), - description: t("orders.fields.orderNumber"), - headerAlign: "center", - align: "center", - flex: 1, - minWidth: 100, + headerName: t("orders.fields.order"), + description: t("orders.fields.order"), + renderCell: function render({ row }) { + return #{row.orderNumber}; + }, }, { field: "status.text", headerName: t("orders.fields.status"), - headerAlign: "center", - align: "center", + width: 124, renderCell: function render({ row }) { return ; }, - flex: 1, - minWidth: 100, + }, + { + field: "products", + headerName: t("orders.fields.products"), + width: 184, + sortable: false, + renderCell: function render({ row }) { + return ; + }, }, { field: "amount", headerName: t("orders.fields.amount"), headerAlign: "center", - align: "center", + align: "right", + width: 120, renderCell: function render({ row }) { return ( = () => { currency: "USD", style: "currency", }} - value={row.amount / 100} - sx={{ fontSize: "14px" }} + value={row.amount} /> ); }, - flex: 1, - minWidth: 100, }, { field: "store", headerName: t("orders.fields.store"), + width: 154, valueGetter: ({ row }) => row.store.title, - flex: 1, - minWidth: 150, sortable: false, }, { - field: "user", - headerName: t("orders.fields.user"), + field: "user.fullName", + headerName: t("orders.fields.customer"), + width: 154, valueGetter: ({ row }) => row.user.fullName, - flex: 1, - minWidth: 150, - sortable: false, - }, - { - field: "products", - headerName: t("orders.fields.products"), - headerAlign: "center", - align: "center", sortable: false, - renderCell: function render({ row }) { - return ( - - {row.products.map((product) => ( -
  • {product.name}
  • - ))} - - } - > - - {t("orders.fields.itemsAmount", { - amount: row.products.length, - })} - -
    - ); - }, - flex: 1, - minWidth: 100, }, + { field: "createdAt", headerName: t("orders.fields.createdAt"), - flex: 1, - minWidth: 170, + width: 200, renderCell: function render({ row }) { - return ( - - ); + return ; }, }, { field: "actions", type: "actions", headerName: t("table.actions"), - flex: 1, - minWidth: 100, sortable: false, + headerAlign: "right", + align: "right", getActions: ({ id }) => [ = () => { const { show } = useNavigation(); const { isLoading, triggerExport } = useExport({ - sorter, + sorters, filters, pageSize: 50, maxItemCount: 50, @@ -255,191 +173,33 @@ export const OrderList: React.FC = () => { }, }); - const { register, handleSubmit, control } = useForm< - BaseRecord, - HttpError, - IOrderFilterVariables - >({ - defaultValues: { - status: getDefaultFilter("status.text", filters, "in"), - q: getDefaultFilter("q", filters, "eq"), - store: getDefaultFilter("store.id", filters, "eq"), - user: getDefaultFilter("user.id", filters, "eq"), - }, - }); - - const { autocompleteProps: storeAutocompleteProps } = useAutocomplete({ - resource: "stores", - defaultValue: getDefaultFilter("store.id", filters, "eq"), - }); - - const { autocompleteProps: orderAutocompleteProps } = useAutocomplete({ - resource: "orderStatuses", - }); - - const { autocompleteProps: userAutocompleteProps } = useAutocomplete({ - resource: "users", - defaultValue: getDefaultFilter("user.id", filters, "eq"), - }); - return ( - - - - - - - - ( - { - field.onChange(value.map((p) => p.text ?? p)); - }} - getOptionLabel={(item) => { - return item?.text ? item.text : item; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} - /> - )} - /> - ( - { - field.onChange(value?.id ?? value); - }} - getOptionLabel={(item) => { - return item.title - ? item.title - : storeAutocompleteProps?.options?.find( - (p) => p.id.toString() === item.toString(), - )?.title ?? ""; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} - /> - )} - /> - ( - { - field.onChange(value?.id ?? value); - }} - getOptionLabel={(item) => { - return item.fullName - ? item.fullName - : userAutocompleteProps?.options?.find( - (p) => p.id.toString() === item.toString(), - )?.fullName ?? ""; - }} - isOptionEqualToValue={(option, value) => - value === undefined || - option?.id?.toString() === - (value?.id ?? value)?.toString() - } - renderInput={(params) => ( - - )} - /> - )} - /> -
    - -
    -
    -
    -
    - - - ), + + } + > + + { + show("orders", id); }} - > - { - show("orders", id); - }} - pageSizeOptions={[10, 20, 50, 100]} - sx={{ - ...dataGridProps.sx, - "& .MuiDataGrid-row": { - cursor: "pointer", - }, - }} - /> - - -
    + pageSizeOptions={[10, 20, 50, 100]} + sx={{ + "& .MuiDataGrid-row": { + cursor: "pointer", + }, + }} + /> + + ); }; diff --git a/examples/finefoods-material-ui/src/pages/orders/show.tsx b/examples/finefoods-material-ui/src/pages/orders/show.tsx index f3d659d5545a..042daec8cfa2 100644 --- a/examples/finefoods-material-ui/src/pages/orders/show.tsx +++ b/examples/finefoods-material-ui/src/pages/orders/show.tsx @@ -1,40 +1,29 @@ import React from "react"; -import { DataGrid, GridColDef } from "@mui/x-data-grid"; import { IResourceComponentsProps, - useNavigation, useShow, useTranslate, useUpdate, } from "@refinedev/core"; -import { List } from "@refinedev/mui"; -import dayjs from "dayjs"; - +import { ListButton } from "@refinedev/mui"; import { useTheme } from "@mui/material/styles"; -import Avatar from "@mui/material/Avatar"; -import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; -import Card from "@mui/material/Card"; -import CardContent from "@mui/material/CardContent"; -import CardHeader from "@mui/material/CardHeader"; -import IconButton from "@mui/material/IconButton"; -import Paper from "@mui/material/Paper"; import Stack from "@mui/material/Stack"; -import Step from "@mui/material/Step"; -import StepLabel from "@mui/material/StepLabel"; -import Stepper from "@mui/material/Stepper"; import Typography from "@mui/material/Typography"; -import useMediaQuery from "@mui/material/useMediaQuery"; - -import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import CheckOutlinedIcon from "@mui/icons-material/CheckOutlined"; import CloseOutlinedIcon from "@mui/icons-material/CloseOutlined"; -import MopedIcon from "@mui/icons-material/Moped"; -import PhoneIphoneIcon from "@mui/icons-material/PhoneIphone"; - -import { CourierInfoBox, Map, MapMarker } from "../../components"; -import { useOrderCustomKbarActions } from "../../hooks"; -import { IEvent, IOrder, IProduct } from "../../interfaces"; +import ArrowBack from "@mui/icons-material/ArrowBack"; +import Divider from "@mui/material/Divider"; +import Grid from "@mui/material/Unstable_Grid2"; +import Paper from "@mui/material/Paper"; +import { + OrderDeliveryMap, + OrderDetails, + OrderProducts, + Card, +} from "../../components"; +import { RefineListView } from "../../components"; +import { IOrder } from "../../interfaces"; export const OrderShow: React.FC = () => { const t = useTranslate(); @@ -47,68 +36,10 @@ export const OrderShow: React.FC = () => { record?.status.text === "Ready" || record?.status.text === "On The Way"; - const { goBack } = useNavigation(); const { mutate } = useUpdate(); const theme = useTheme(); - const isSmallOrLess = useMediaQuery(theme.breakpoints.down("sm")); - - const columns = React.useMemo[]>( - () => [ - { - field: "name", - headerName: t("orders.deliverables.fields.items"), - width: 300, - renderCell: function render({ row }) { - return ( - - - - - {row.name} - - #{row.id} - - - ); - }, - }, - { - field: "quantity", - headerName: t("orders.deliverables.fields.quantity"), - width: 150, - sortable: false, - valueGetter: () => "1x", - }, - { - field: "price", - headerName: t("orders.deliverables.fields.price"), - width: 100, - type: "number", - }, - { - field: "price", - headerName: t("orders.deliverables.fields.total"), - width: 100, - type: "number", - }, - ], - [t], - ); - - const CustomFooter = () => ( - - - {t("orders.deliverables.mainTotal")} - - {record?.amount}$ - - ); - const handleMutate = (status: { id: number; text: string }) => { if (record) { mutate({ @@ -121,186 +52,92 @@ export const OrderShow: React.FC = () => { } }; - useOrderCustomKbarActions(record); - return ( - - - - {t("orders.fields.orderID")} - {`#${ - record?.orderNumber ?? "" - }`} - - } - avatar={ - - - - } - action={ - - - - - } - /> - - el.status === record?.status?.text, - )} - orientation={isSmallOrLess ? "vertical" : "horizontal"} - > - {record?.events.map((event: IEvent, index: number) => ( - - - {event.date && dayjs(event.date).format("L LT")} - - } - error={event.status === "Cancelled"} - > - {event.status} - - - ))} - - - - - - - - - - - - - - - - - COURIER - - {record?.courier.name} {record?.courier.surname} - - - ID #{record?.courier.id} - - - - - } - value={record?.courier.gsm} - /> - } - value="15:05" - /> - - - - - + } + /> + + + {t("orders.order")} #{record?.orderNumber} + + } + headerButtons={[ + + + + , + ]} > - - - + + + + + + + + + + + + + + + + + ); }; diff --git a/examples/finefoods-material-ui/src/pages/products/create.tsx b/examples/finefoods-material-ui/src/pages/products/create.tsx new file mode 100644 index 000000000000..332f793db2a6 --- /dev/null +++ b/examples/finefoods-material-ui/src/pages/products/create.tsx @@ -0,0 +1,5 @@ +import { ProductDrawerForm } from "../../components"; + +export const ProductCreate = () => { + return ; +}; diff --git a/examples/finefoods-material-ui/src/pages/products/edit.tsx b/examples/finefoods-material-ui/src/pages/products/edit.tsx new file mode 100644 index 000000000000..45c26471eab6 --- /dev/null +++ b/examples/finefoods-material-ui/src/pages/products/edit.tsx @@ -0,0 +1,5 @@ +import { ProductDrawerForm } from "../../components"; + +export const ProductEdit = () => { + return ; +}; diff --git a/examples/finefoods-material-ui/src/pages/products/index.tsx b/examples/finefoods-material-ui/src/pages/products/index.tsx index 491ccf0c1246..205f1937104d 100644 --- a/examples/finefoods-material-ui/src/pages/products/index.tsx +++ b/examples/finefoods-material-ui/src/pages/products/index.tsx @@ -1 +1,3 @@ export * from "./list"; +export * from "./edit"; +export * from "./create"; diff --git a/examples/finefoods-material-ui/src/pages/products/list.tsx b/examples/finefoods-material-ui/src/pages/products/list.tsx index 35026442812a..8c45df1d37be 100644 --- a/examples/finefoods-material-ui/src/pages/products/list.tsx +++ b/examples/finefoods-material-ui/src/pages/products/list.tsx @@ -1,190 +1,107 @@ -import React from "react"; -import { - useTranslate, - IResourceComponentsProps, - useTable, - getDefaultFilter, - HttpError, -} from "@refinedev/core"; -import { useModalForm } from "@refinedev/react-hook-form"; -import { CreateButton } from "@refinedev/mui"; - -import Grid from "@mui/material/Grid"; +import React, { PropsWithChildren, useState } from "react"; +import { useTranslate, useGo, useNavigation, useList } from "@refinedev/core"; +import { CreateButton, useDataGrid } from "@refinedev/mui"; +import ListOutlinedIcon from "@mui/icons-material/ListOutlined"; +import BorderAllOutlinedIcon from "@mui/icons-material/BorderAllOutlined"; +import { useLocation } from "react-router-dom"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; +import ToggleButton from "@mui/material/ToggleButton"; import Paper from "@mui/material/Paper"; -import Typography from "@mui/material/Typography"; -import InputBase from "@mui/material/InputBase"; -import IconButton from "@mui/material/IconButton"; -import Stack from "@mui/material/Stack"; -import Pagination from "@mui/material/Pagination"; -import SearchIcon from "@mui/icons-material/Search"; - import { - CategoryFilter, - ProductItem, - CreateProduct, - EditProduct, + ProductListTable, + ProductListCard, + RefineListView, } from "../../components"; -import { IProduct, Nullable } from "../../interfaces"; +import { ICategory, IProduct } from "../../interfaces"; -export const ProductList: React.FC = () => { - const t = useTranslate(); - - const { tableQueryResult, setFilters, setCurrent, filters, pageCount } = - useTable({ - resource: "products", - initialPageSize: 12, - }); +type View = "table" | "card"; - const createDrawerFormProps = useModalForm< - IProduct, - HttpError, - Nullable - >({ - refineCoreProps: { action: "create" }, +export const ProductList = ({ children }: PropsWithChildren) => { + const [view, setView] = useState(() => { + const view = localStorage.getItem("product-view") as View; + return view || "table"; }); - const { - modal: { show: showCreateDrawer }, - } = createDrawerFormProps; + const go = useGo(); + const { replace } = useNavigation(); + const { pathname } = useLocation(); + const { createUrl } = useNavigation(); + const t = useTranslate(); - const editDrawerFormProps = useModalForm< - IProduct, - HttpError, - Nullable - >({ - refineCoreProps: { action: "edit" }, + const dataGrid = useDataGrid({ + resource: "products", + initialPageSize: 12, }); - const { - modal: { show: showEditDrawer }, - } = editDrawerFormProps; + const { data: categoriesData } = useList({ + resource: "categories", + pagination: { + mode: "off", + }, + }); + const categories = categoriesData?.data || []; + + const handleViewChange = ( + _e: React.MouseEvent, + newView: View, + ) => { + // remove query params (pagination, filters, etc.) when changing view + replace(""); - const products: IProduct[] = tableQueryResult.data?.data || []; + setView(newView); + localStorage.setItem("product-view", newView); + }; return ( <> - - - - - - - {t("products.products")} - - ) => { - setFilters([ - { - field: "name", - operator: "contains", - value: - e.target.value !== "" ? e.target.value : undefined, - }, - ]); - }} - /> - - - - - showCreateDrawer()} - variant="outlined" - sx={{ marginBottom: "5px" }} - > - {t("stores.buttons.addProduct")} - - - - {products.length > 0 ? ( - products.map((product: IProduct) => ( - - - - )) - ) : ( - - - {t("products.noProducts")} - - - )} - - , page: number) => { - event.preventDefault(); - setCurrent(page); - }} - /> - - [ + + + + + + + + , + { + return go({ + to: `${createUrl("products")}`, + query: { + to: pathname, + }, + options: { + keepQuery: true, + }, + type: "replace", + }); }} > - - - {t("stores.tagFilterDescription")} - - - - - - + {t("products.actions.add")} + , + ]} + > + {view === "table" && ( + + + + )} + {view === "card" && ( + + )} + + {children} ); }; diff --git a/examples/finefoods-material-ui/src/pages/reviews/index.ts b/examples/finefoods-material-ui/src/pages/reviews/index.ts deleted file mode 100644 index 491ccf0c1246..000000000000 --- a/examples/finefoods-material-ui/src/pages/reviews/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./list"; diff --git a/examples/finefoods-material-ui/src/pages/reviews/list.tsx b/examples/finefoods-material-ui/src/pages/reviews/list.tsx deleted file mode 100644 index 99f59927d8bc..000000000000 --- a/examples/finefoods-material-ui/src/pages/reviews/list.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import React from "react"; -import { - BaseKey, - IResourceComponentsProps, - useNavigation, - useTranslate, - useUpdateMany, -} from "@refinedev/core"; - -import Check from "@mui/icons-material/Check"; -import Clear from "@mui/icons-material/Clear"; -import Avatar from "@mui/material/Avatar"; -import Button from "@mui/material/Button"; -import Rating from "@mui/material/Rating"; -import Stack from "@mui/material/Stack"; -import Tooltip from "@mui/material/Tooltip"; -import Typography from "@mui/material/Typography"; -import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid"; -import { List, useDataGrid } from "@refinedev/mui"; - -import { IReview } from "../../interfaces"; - -export const ReviewsList: React.FC = () => { - const [selectedRowKeys, setSelectedRowKeys] = React.useState([]); - const hasSelected = selectedRowKeys.length > 0; - - const t = useTranslate(); - const { show } = useNavigation(); - - const { mutate } = useUpdateMany(); - - const { dataGridProps } = useDataGrid({ - initialPageSize: 10, - permanentFilter: [ - { - field: "status", - operator: "eq", - value: "pending", - }, - ], - }); - - const handleUpdate = (id: BaseKey, status: IReview["status"]) => { - mutate({ - resource: "reviews", - ids: [id], - values: { status }, - mutationMode: "undoable", - }); - }; - - const updateSelectedItems = (status: "approved" | "rejected") => { - mutate( - { - resource: "reviews", - ids: selectedRowKeys.map(String), - values: { - status, - }, - mutationMode: "undoable", - }, - { - onSuccess: () => { - setSelectedRowKeys([]); - }, - }, - ); - }; - - const columns = React.useMemo[]>( - () => [ - { - field: "avatar", - headerName: "Avatar", - renderCell: function render({ row }) { - return ; - }, - sortable: false, - }, - { - field: "user", - headerName: t("reviews.fields.user"), - valueGetter: ({ row }) => row.user.fullName, - minWidth: 200, - flex: 1, - sortable: false, - }, - { - field: "order.id", - headerName: t("reviews.fields.orderId"), - renderCell: function render({ row }) { - return ( - - ); - }, - minWidth: 100, - flex: 0.5, - }, - { - field: "comment", - headerName: t("reviews.fields.review"), - renderCell: function render({ row }) { - return ( - - - {row.comment[0]} - - - ); - }, - minWidth: 200, - flex: 1, - }, - { - field: "star", - headerName: t("reviews.fields.rating"), - headerAlign: "center", - flex: 1, - minWidth: 250, - align: "center", - renderCell: function render({ row }) { - return ( - - {row.star} - - - ); - }, - }, - { - field: "actions", - headerName: t("table.actions"), - type: "actions", - getActions: ({ row }) => [ - } - onClick={() => handleUpdate(row.id, "approved")} - showInMenu - />, - } - onClick={() => handleUpdate(row.id, "rejected")} - showInMenu - />, - ], - }, - ], - [t], - ); - - return ( - - - - - ), - }} - > - { - setSelectedRowKeys(newSelectionModel as React.Key[]); - }} - pageSizeOptions={[10, 20, 50, 100]} - rowSelectionModel={selectedRowKeys} - /> - - ); -}; diff --git a/examples/finefoods-material-ui/src/pages/stores/create.tsx b/examples/finefoods-material-ui/src/pages/stores/create.tsx index bcfb2a198f3e..f3c76b6c229b 100644 --- a/examples/finefoods-material-ui/src/pages/stores/create.tsx +++ b/examples/finefoods-material-ui/src/pages/stores/create.tsx @@ -1,249 +1,5 @@ -import { - HttpError, - IResourceComponentsProps, - useTranslate, -} from "@refinedev/core"; -import { useForm } from "@refinedev/react-hook-form"; -import { Controller } from "react-hook-form"; -import { Create } from "@refinedev/mui"; -import InputMask from "react-input-mask"; +import { StoreForm } from "../../components"; -import FormControl from "@mui/material/FormControl"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Avatar from "@mui/material/Avatar"; -import FormLabel from "@mui/material/FormLabel"; -import Grid from "@mui/material/Grid"; -import Radio from "@mui/material/Radio"; -import RadioGroup from "@mui/material/RadioGroup"; -import Stack from "@mui/material/Stack"; -import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; -import FormHelperText from "@mui/material/FormHelperText"; -import type { TextFieldProps } from "@mui/material/TextField"; - -import { IStore } from "../../interfaces"; - -export const StoreCreate: React.FC = () => { - const t = useTranslate(); - const { - register, - control, - formState: { errors }, - saveButtonProps, - } = useForm(); - - return ( - -
    - - - - - - {t("stores.selectLocation")} - - - - - - - - {t("stores.fields.title")} - - - {errors.title && ( - {errors.title.message} - )} - - - - {t("stores.fields.email")} - - - {errors.email && ( - {errors.email.message} - )} - - - - {t("stores.fields.gsm")} - - ( - - {/* @ts-expect-error False alarm */} - {(props: TextFieldProps) => ( - - )} - - )} - /> - {errors.gsm && ( - {errors.gsm.message} - )} - - - - {t("products.fields.isActive")} - - ( - - } - label={t("status.enable")} - /> - } - label={t("status.disable")} - /> - - )} - /> - {errors.isActive && ( - - {errors.isActive.message} - - )} - - - - - - - {t("stores.fields.address")} - - - {errors.address && ( - - {errors.address.text?.message} - - )} - - - -
    -
    - ); +export const StoreCreate = () => { + return ; }; diff --git a/examples/finefoods-material-ui/src/pages/stores/edit.tsx b/examples/finefoods-material-ui/src/pages/stores/edit.tsx index 8f615fefff0e..12209f45a394 100644 --- a/examples/finefoods-material-ui/src/pages/stores/edit.tsx +++ b/examples/finefoods-material-ui/src/pages/stores/edit.tsx @@ -1,266 +1,5 @@ -import { - HttpError, - IResourceComponentsProps, - useTranslate, -} from "@refinedev/core"; -import { useForm } from "@refinedev/react-hook-form"; -import { Controller } from "react-hook-form"; -import { Edit } from "@refinedev/mui"; -import InputMask from "react-input-mask"; +import { StoreForm } from "../../components"; -import FormControl from "@mui/material/FormControl"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Avatar from "@mui/material/Avatar"; -import FormLabel from "@mui/material/FormLabel"; -import Grid from "@mui/material/Grid"; -import Radio from "@mui/material/Radio"; -import RadioGroup from "@mui/material/RadioGroup"; -import Stack from "@mui/material/Stack"; -import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; -import FormHelperText from "@mui/material/FormHelperText"; -import type { TextFieldProps } from "@mui/material/TextField"; - -import { IStore } from "../../interfaces"; - -export const StoreEdit: React.FC = () => { - const t = useTranslate(); - const { - register, - control, - refineCore: { formLoading }, - formState: { errors }, - saveButtonProps, - setValue, - } = useForm(); - - return ( - -
    - - - - - - {t("stores.selectLocation")} - - - - - - - - {t("stores.fields.title")} - - - {errors.title && ( - {errors.title.message} - )} - - - - {t("stores.fields.email")} - - - {errors.email && ( - {errors.email.message} - )} - - - - {t("stores.fields.gsm")} - - ( - - {/* @ts-expect-error False alarm */} - {(props: TextFieldProps) => ( - - )} - - )} - /> - {errors.gsm && ( - {errors.gsm.message} - )} - - - - {t("products.fields.isActive")} - - ( - { - const value = event.target.value === "true"; - - setValue("isActive", value, { - shouldValidate: true, - }); - - return value; - }} - row - > - } - label={t("status.enable")} - /> - } - label={t("status.disable")} - /> - - )} - /> - {errors.isActive && ( - - {errors.isActive.message} - - )} - - - - - - - {t("stores.fields.address")} - - - {errors.address && ( - - {errors.address.text?.message} - - )} - - - -
    -
    - ); +export const StoreEdit = () => { + return ; }; diff --git a/examples/finefoods-material-ui/src/pages/stores/list.tsx b/examples/finefoods-material-ui/src/pages/stores/list.tsx index 7635cccad591..b52b5354c3f9 100644 --- a/examples/finefoods-material-ui/src/pages/stores/list.tsx +++ b/examples/finefoods-material-ui/src/pages/stores/list.tsx @@ -1,159 +1,85 @@ -import React from "react"; -import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid"; -import { - IResourceComponentsProps, - useModal, - useNavigation, - useShow, - useTranslate, -} from "@refinedev/core"; -import { - BooleanField, - DateField, - List, - TextFieldComponent, - useDataGrid, -} from "@refinedev/mui"; +import React, { useState } from "react"; +import { useGo, useNavigation, useTranslate } from "@refinedev/core"; +import { CreateButton } from "@refinedev/mui"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; +import ToggleButton from "@mui/material/ToggleButton"; +import Box from "@mui/material/Box"; +import ListOutlinedIcon from "@mui/icons-material/ListOutlined"; +import PlaceOutlinedIcon from "@mui/icons-material/PlaceOutlined"; +import { RefineListView, StoreTable, AllStoresMap } from "../../components"; +import { useLocation } from "react-router-dom"; -import Avatar from "@mui/material/Avatar"; -import Paper from "@mui/material/Paper"; -import EditOutlined from "@mui/icons-material/EditOutlined"; +type View = "table" | "map"; -import { StoreProducts } from "../../components/store"; -import { IStore } from "../../interfaces"; +export const StoreList = () => { + const [view, setView] = useState(() => { + const view = localStorage.getItem("store-view") as View; + return view || "table"; + }); -export const StoreList: React.FC = () => { + const go = useGo(); + const { replace } = useNavigation(); + const { pathname } = useLocation(); + const { createUrl } = useNavigation(); const t = useTranslate(); - const { edit } = useNavigation(); - - const { show, visible, close } = useModal(); - - const { queryResult, setShowId } = useShow(); - const { data: showQueryResult } = queryResult; - const record = showQueryResult?.data; + const handleViewChange = ( + _e: React.MouseEvent, + newView: View, + ) => { + // remove query params (pagination, filters, etc.) when changing view + replace(""); - const { dataGridProps } = useDataGrid({ - initialPageSize: 10, - }); - - const columns = React.useMemo[]>( - () => [ - { - field: "avatar", - headerName: "", - align: "center", - renderCell: function render() { - return ( - - ); - }, - }, - { - field: "id", - align: "center", - headerName: t("stores.fields.id"), - }, - { - field: "title", - headerName: t("stores.fields.title"), - flex: 1, - minWidth: 160, - }, - { - field: "email", - headerName: t("stores.fields.email"), - flex: 2, - minWidth: 300, - }, - { - field: "gsm", - headerName: t("stores.fields.gsm"), - flex: 1, - minWidth: 150, - }, - { - field: "address", - headerName: t("stores.fields.address"), - flex: 2, - minWidth: 300, - renderCell: function render({ row }) { - return ; - }, - }, - { - field: "createdAt", - headerName: t("stores.fields.createdAt"), - flex: 1, - minWidth: 100, - renderCell: function render({ row }) { - return ; - }, - }, - { - field: "isActive", - headerName: t("stores.fields.isActive"), - flex: 0.5, - align: "center", - headerAlign: "center", - renderCell: function render({ row }) { - return ( - - ); - }, - }, - { - field: "actions", - headerName: t("table.actions"), - type: "actions", - getActions: ({ row }) => [ - } - showInMenu - onClick={() => edit("stores", row.id)} - />, - { - show(); - setShowId(row.id); - }} - key={2} - label={t("stores.buttons.edit")} - icon={} - showInMenu - />, - ], - }, - ], - [t], - ); + setView(newView); + localStorage.setItem("store-view", newView); + }; return ( - - - - - {record && ( - + [ + + + + + + + + , + { + return go({ + to: `${createUrl("stores")}`, + query: { + to: pathname, + }, + options: { + keepQuery: true, + }, + type: "replace", + }); + }} + > + {t("stores.addNewStore")} + , + ]} + > + {view === "table" && } + {view === "map" && ( + + +

    hello

    +
    )} -
    + ); }; diff --git a/examples/finefoods-material-ui/src/pages/users/list.tsx b/examples/finefoods-material-ui/src/pages/users/list.tsx deleted file mode 100644 index 43b28e391a3f..000000000000 --- a/examples/finefoods-material-ui/src/pages/users/list.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React from "react"; -import { - CrudFilters, - getDefaultFilter, - HttpError, - IResourceComponentsProps, - useTranslate, -} from "@refinedev/core"; -import { - BooleanField, - DateField, - List, - ShowButton, - useDataGrid, -} from "@refinedev/mui"; -import { useForm } from "@refinedev/react-hook-form"; -import { Controller } from "react-hook-form"; -import { DataGrid, GridColDef } from "@mui/x-data-grid"; - -import Avatar from "@mui/material/Avatar"; -import Box from "@mui/material/Box"; -import Button from "@mui/material/Button"; -import Card from "@mui/material/Card"; -import CardContent from "@mui/material/CardContent"; -import CardHeader from "@mui/material/CardHeader"; -import FormControl from "@mui/material/FormControl"; -import Grid from "@mui/material/Grid"; -import InputAdornment from "@mui/material/InputAdornment"; -import InputLabel from "@mui/material/InputLabel"; -import MenuItem from "@mui/material/MenuItem"; -import Select from "@mui/material/Select"; -import TextField from "@mui/material/TextField"; - -import SearchOutlinedIcon from "@mui/icons-material/SearchOutlined"; - -import { IUser, IUserFilterVariables } from "../../interfaces"; - -export const UserList: React.FC = () => { - const t = useTranslate(); - - const { dataGridProps, search, filters } = useDataGrid< - IUser, - HttpError, - IUserFilterVariables - >({ - initialPageSize: 10, - onSearch: (params) => { - const filters: CrudFilters = []; - const { q, gender, isActive } = params; - - filters.push({ - field: "q", - operator: "eq", - value: q !== "" ? q : undefined, - }); - - filters.push({ - field: "gender", - operator: "eq", - value: gender !== "" ? gender : undefined, - }); - - filters.push({ - field: "isActive", - operator: "eq", - value: isActive !== "" ? isActive : undefined, - }); - - return filters; - }, - }); - - const columns = React.useMemo[]>( - () => [ - { - field: "gsm", - headerName: t("users.fields.gsm"), - minWidth: 150, - flex: 1, - }, - { - field: "avatar", - headerName: t("users.fields.avatar.label"), - renderCell: function render({ row }) { - return ; - }, - minWidth: 100, - flex: 1, - sortable: false, - }, - { - field: "firstName", - headerName: t("users.fields.firstName"), - minWidth: 150, - flex: 1, - }, - { - field: "lastName", - headerName: t("users.fields.lastName"), - minWidth: 150, - flex: 1, - }, - { - field: "gender", - headerName: t("users.fields.gender.label"), - valueGetter: ({ row }) => t(`users.fields.gender.${row.gender}`), - }, - { - field: "isActive", - headerName: t("users.fields.isActive.label"), - align: "center", - headerAlign: "center", - renderCell: function render({ row }) { - return ( - - ); - }, - minWidth: 80, - flex: 0.5, - }, - { - field: "createdAt", - headerName: t("users.fields.createdAt"), - renderCell: function render({ row }) { - return ; - }, - minWidth: 200, - flex: 1, - }, - { - field: "actions", - headerName: t("table.actions"), - renderCell: function render({ row }) { - return ; - }, - align: "center", - headerAlign: "center", - flex: 1, - minWidth: 80, - }, - ], - [t], - ); - - const { register, handleSubmit, control } = useForm< - IUser, - HttpError, - IUserFilterVariables - >({ - defaultValues: { - q: getDefaultFilter("q", filters, "eq"), - gender: getDefaultFilter("gender", filters, "eq") || "", - isActive: getDefaultFilter("isActive", filters, "eq") || "", - }, - }); - - return ( - - - - - - - - - - ), - }} - /> - ( - - - {t("users.filter.gender.label")} - - - - )} - /> - ( - - - {t("users.filter.isActive.label")} - - - - )} - /> - -
    - -
    -
    -
    -
    - - - - - -
    - ); -}; diff --git a/examples/finefoods-material-ui/src/pages/users/show.tsx b/examples/finefoods-material-ui/src/pages/users/show.tsx deleted file mode 100644 index e0fd78994dee..000000000000 --- a/examples/finefoods-material-ui/src/pages/users/show.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import React from "react"; -import { - HttpError, - IResourceComponentsProps, - useShow, - useTranslate, -} from "@refinedev/core"; -import { DataGrid, GridColDef } from "@mui/x-data-grid"; -import { DateField, List, NumberField, useDataGrid } from "@refinedev/mui"; - -import Avatar from "@mui/material/Avatar"; -import Grid from "@mui/material/Grid"; -import Paper from "@mui/material/Paper"; -import Stack from "@mui/material/Stack"; -import Table from "@mui/material/Table"; -import TableBody from "@mui/material/TableBody"; -import TableCell from "@mui/material/TableCell"; -import TableContainer from "@mui/material/TableContainer"; -import TableHead from "@mui/material/TableHead"; -import TableRow from "@mui/material/TableRow"; -import Typography from "@mui/material/Typography"; - -import CheckOutlinedIcon from "@mui/icons-material/CheckOutlined"; -import DateRangeOutlinedIcon from "@mui/icons-material/DateRangeOutlined"; -import LocalPhoneOutlinedIcon from "@mui/icons-material/LocalPhoneOutlined"; -import PersonOutlineOutlinedIcon from "@mui/icons-material/PersonOutlineOutlined"; - -import { CustomTooltip, OrderStatus } from "../../components"; -import { IOrder, IOrderFilterVariables, IUser } from "../../interfaces"; - -const UserInfoText: React.FC<{ children: React.ReactNode }> = ({ - children, -}) => ( - - {children} - -); - -export const UserShow: React.FC = () => { - const t = useTranslate(); - - const { queryResult } = useShow(); - const user = queryResult.data?.data; - - const { dataGridProps } = useDataGrid< - IOrder, - HttpError, - IOrderFilterVariables - >({ - resource: "orders", - initialSorter: [ - { - field: "createdAt", - order: "desc", - }, - ], - permanentFilter: [ - { - field: "user.id", - operator: "eq", - value: user?.id, - }, - ], - initialPageSize: 4, - queryOptions: { - enabled: user !== undefined, - }, - syncWithLocation: false, - }); - - const columns = React.useMemo[]>( - () => [ - { - field: "orderNumber", - headerName: t("orders.fields.orderNumber"), - width: 150, - }, - { - field: "status.text", - headerName: t("orders.fields.status"), - renderCell: function render({ row }) { - return ; - }, - width: 100, - }, - { - field: "amount", - align: "right", - headerAlign: "right", - headerName: t("orders.fields.amount"), - renderCell: function render({ row }) { - return ( - - ); - }, - width: 100, - }, - { - field: "store", - headerName: t("orders.fields.store"), - width: 150, - valueGetter: ({ row }) => row.store.title, - sortable: false, - }, - { - field: "user", - headerName: t("orders.fields.user"), - valueGetter: ({ row }) => row.user.fullName, - sortable: false, - }, - { - field: "products", - headerName: t("orders.fields.products"), - flex: 1, - headerAlign: "center", - align: "center", - sortable: false, - renderCell: function render({ row }) { - return ( - - {row.products.map((product) => ( -
  • {product.name}
  • - ))} - - } - > - - {t("orders.fields.itemsAmount", { - amount: row.products.length, - })} - -
    - ); - }, - }, - { - field: "createdAt", - headerName: t("orders.fields.createdAt"), - flex: 1, - renderCell: function render({ row }) { - return ( - - ); - }, - }, - ], - [t], - ); - - return ( - - - - - - - {user?.firstName} {user?.lastName} - - -
    - - - - - {t(`users.fields.gender.${user?.gender}`)} - - - - - {user?.gsm} - - - - {user?.createdAt} - - - - - {user?.isActive - ? t("users.fields.isActive.true") - : t("users.fields.isActive.false")} - - - -
    -
    - - - - - - - - - - {t("users.addresses.address")} - - - - {user?.addresses.map((row) => ( - - {row.text} - - ))} - -
    -
    -
    -
    -
    - ); -}; diff --git a/examples/finefoods-material-ui/src/theme.ts b/examples/finefoods-material-ui/src/theme.ts index 9b3e57872ee9..c0590ee64612 100644 --- a/examples/finefoods-material-ui/src/theme.ts +++ b/examples/finefoods-material-ui/src/theme.ts @@ -1,46 +1,47 @@ -import { - DarkTheme as DefaultDarkTheme, - LightTheme as DefaultLightTheme, -} from "@refinedev/mui"; - +import { RefineThemes } from "@refinedev/mui"; import { createTheme, responsiveFontSizes } from "@mui/material/styles"; +import gray from "@mui/material/colors/grey"; const LightTheme = createTheme({ - ...DefaultLightTheme, - timeLine: { - color: { - pending: "#fff7e6", - ready: "#e6fffb", - delivered: "#e6f7ff", - cancelled: "#fff1f0", - onTheWay: "#f6ffed", + ...RefineThemes.Orange, + components: { + ...RefineThemes.OrangeDark.components, + MuiCssBaseline: { + styleOverrides: { + "main.MuiBox-root": { + backgroundColor: gray[100], + }, + body: { + backgroundColor: gray[100], + }, + }, }, - dotColor: { - pending: "#ffa940", - ready: "#36cfc9", - delivered: "#40a9ff", - cancelled: "#ff4d4f", - onTheWay: "#73d13d", + MuiTypography: { + defaultProps: { + variant: "body2", + }, }, }, }); const DarkTheme = createTheme({ - ...DefaultDarkTheme, - timeLine: { - color: { - pending: "#f2a400", - ready: "#00c2a2", - delivered: "#0083c2", - cancelled: "#c60d00", - onTheWay: "#62c400", + ...RefineThemes.OrangeDark, + components: { + ...RefineThemes.OrangeDark.components, + MuiCssBaseline: { + styleOverrides: { + "main.MuiBox-root": { + backgroundColor: "#121212", + }, + body: { + backgroundColor: "#121212", + }, + }, }, - dotColor: { - pending: "#9f5700", - ready: "#196966", - delivered: "#00579f", - cancelled: "#a60001", - onTheWay: "#386d19", + MuiTypography: { + defaultProps: { + variant: "body2", + }, }, }, }); diff --git a/examples/finefoods-material-ui/src/utils/geocoding/index.ts b/examples/finefoods-material-ui/src/utils/geocoding/index.ts new file mode 100644 index 000000000000..1e64f69ee9df --- /dev/null +++ b/examples/finefoods-material-ui/src/utils/geocoding/index.ts @@ -0,0 +1,79 @@ +const API_URL = "https://geocode.maps.co"; +const API_KEY = "65caa5869275c294478503twc82c790"; + +export type LatLng = { + lat: number; + lng: number; +}; + +export type Place = { + place_id: number; + licence: string; + osm_type: string; + osm_id: number; + lat: string; + lon: string; + display_name: string; + address: { + amenity: string; + road: string; + neighbourhood: string; + suburb: string; + city: string; + state: string; + "ISO3166-2-lvl4": string; + postcode: string; + country: string; + country_code: string; + }; + boundingbox: Array; +}; + +export const getAddressWithLatLng = async ({ lat, lng }: LatLng) => { + try { + const query = `lat=${lat}&lon=${lng}&api_key=${API_KEY}`; + const response = await fetch(`${API_URL}/reverse?${query}`); + const data: Place = await response.json(); + if (!data) return null; + + return { + address: data.display_name, + }; + } catch (error) { + return null; + } +}; + +export const getLatLngWithAddress = async (address: string) => { + try { + const query = `q=${address}&api_key=${API_KEY}`; + const response = await fetch(`${API_URL}/search?${query}`); + const data: Place[] = await response.json(); + const lat = data?.[0]?.lat || null; + const lng = data?.[0]?.lon || null; + + if (lat && lng) { + return { + lat, + lng, + }; + } + + return null; + } catch (error) { + return null; + } +}; + +export const convertLatLng = (latLng: { lat: string; lng: string }) => { + const formatter = new Intl.NumberFormat("en", { + maximumFractionDigits: 5, + }); + const lat = Number(formatter.format(Number(latLng.lat))); + const lng = Number(formatter.format(Number(latLng.lng))); + + return { + lat, + lng, + }; +}; diff --git a/examples/finefoods-material-ui/src/utils/index.ts b/examples/finefoods-material-ui/src/utils/index.ts new file mode 100644 index 000000000000..bfbecf1c453c --- /dev/null +++ b/examples/finefoods-material-ui/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./unique-list-with-count"; +export * from "./geocoding"; +export * from "./use-image-upload"; diff --git a/examples/finefoods-material-ui/src/utils/unique-list-with-count.ts b/examples/finefoods-material-ui/src/utils/unique-list-with-count.ts new file mode 100644 index 000000000000..0214c1412b44 --- /dev/null +++ b/examples/finefoods-material-ui/src/utils/unique-list-with-count.ts @@ -0,0 +1,23 @@ +export const getUniqueListWithCount = (props: { + list: TData[]; + field: string; +}) => { + const { list, field } = props; + + const uniqueList = list.reduce( + (acc, item: any) => { + if (!acc[item[field]]) { + acc[item[field]] = { + ...item, + count: 1, + }; + } else { + acc[item[field]].count += 1; + } + return acc; + }, + {} as Record, + ); + + return Object.values(uniqueList); +}; diff --git a/examples/finefoods-material-ui/src/utils/use-image-upload/index.ts b/examples/finefoods-material-ui/src/utils/use-image-upload/index.ts new file mode 100644 index 000000000000..2b0beb2829f5 --- /dev/null +++ b/examples/finefoods-material-ui/src/utils/use-image-upload/index.ts @@ -0,0 +1,37 @@ +import axios from "axios"; +import { IFile } from "../../interfaces"; + +type Props = { + file: FileList[number]; + apiUrl: string; +}; + +export const useImageUpload = async ({ apiUrl, file }: Props) => { + const formData = new FormData(); + formData.append("file", file); + + const res = await axios.post<{ url: string }>( + `${apiUrl}/media/upload`, + formData, + { + withCredentials: false, + headers: { + "Access-Control-Allow-Origin": "*", + }, + }, + ); + + const { name, size, type, lastModified } = file; + + const imagePaylod: IFile[] = [ + { + name, + size, + type, + lastModified, + url: res.data.url, + }, + ]; + + return imagePaylod; +};