Browse Source

implement item actions (delete, edit)

mightyplow 5 năm trước cách đây
mục cha
commit
0d35ce35af

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 3 - 0
src/assets/fonts/simpleCalorieTracker.svg


BIN
src/assets/fonts/simpleCalorieTracker.ttf


BIN
src/assets/fonts/simpleCalorieTracker.woff


+ 6 - 5
src/components/button/IconButton.tsx

@@ -3,17 +3,18 @@ import React, { ReactChild } from 'react';
 import { Icon } from '../icon/Icon';
 import styles from './button.css';
 
-type RawButtonProps = {
+type IconButtonProps = {
   icon: string,
   buttonClassName?: string,
-  iconClassName?: string
+  iconClassName?: string,
+  onClick?: () => void
 };
 
-function IconButton ({icon, buttonClassName, iconClassName}: RawButtonProps) {
+function IconButton ({icon, buttonClassName, iconClassName, onClick}: IconButtonProps) {
   return (
-    <button className={cx(styles.iconButton, buttonClassName)}>
+    <button className={cx(styles.iconButton, buttonClassName)} {...{onClick}}>
       <Icon {...{icon}} className={cx(iconClassName)} />
-    </button>
+    </button >
   );
 }
 

+ 25 - 10
src/components/caloriesInput/CaloriesInput.tsx

@@ -3,7 +3,9 @@ import { IconButton } from '../button/IconButton';
 import styles from './caloriesInput.css';
 
 type CaloriesInputProps = {
-  addCalories: (calories: { title: string, count: number}) => void
+  addCalories: (calories: { title: string, count: number}) => void,
+  saveCalories: (calories: { title: string, count: number}) => void,
+  item?: CalorieValue
 };
 
 interface ICalorieInputForm extends HTMLFormElement {
@@ -11,7 +13,7 @@ interface ICalorieInputForm extends HTMLFormElement {
   caloriesCount: HTMLInputElement;
 }
 
-function CaloriesInput ({ addCalories }: CaloriesInputProps) {
+function CaloriesInput ({ addCalories, saveCalories, item }: CaloriesInputProps) {
   function onSubmit (event: FormEvent<ICalorieInputForm>) {
     event.preventDefault();
 
@@ -19,30 +21,43 @@ function CaloriesInput ({ addCalories }: CaloriesInputProps) {
     const title = form.caloriesTitle.value;
     const count = Number(form.caloriesCount.value) || 0;
 
-    addCalories({ title, count });
+    if (!item) {
+      addCalories({ title, count });
 
-    form.querySelectorAll('input').forEach((formElement) => {
-      formElement.value = '';
-      formElement.blur();
-    });
+      form.querySelectorAll('input').forEach((formElement) => {
+        formElement.value = '';
+        formElement.blur();
+      });
+    } else {
+      saveCalories({ title, count});
+    }
   }
 
+  const {
+    title: itemTitle = '',
+    count: itemCount = ''
+  } = item || {};
+
+  const buttonIcon = item
+    ? 'tick'
+    : 'plus';
+
   return (
     <form onSubmit={onSubmit} className={styles.caloriesInput}>
       <div className={styles.inputWrapper}>
         <div className={styles.formRow}>
           <label htmlFor="caloriesTitle">Nahrungsmittel:</label>
-          <input id="caloriesTitle" name="caloriesTitle" type="text" required />
+          <input id="caloriesTitle" name="caloriesTitle" type="text" defaultValue={itemTitle} required />
         </div>
 
         <div className={styles.formRow}>
           <label htmlFor="caloriesCount">Kalorien:</label>
-          <input id="caloriesCount" name="caloriesCount" type="number" required />
+          <input id="caloriesCount" name="caloriesCount" type="number" defaultValue={itemCount} required />
         </div>
       </div>
 
       <div className={styles.buttonWrapper}>
-        <IconButton icon="plus" buttonClassName={styles.addButton} />
+        <IconButton icon={buttonIcon} buttonClassName={styles.addButton} />
       </div>
     </form>
   );

+ 0 - 2
src/components/caloriesInput/caloriesInput.css

@@ -1,7 +1,5 @@
 .caloriesInput {
     display: flex;
-    margin-top: auto;
-    padding: .5em;
 }
 
 .formRow {

+ 40 - 2
src/components/caloriesList/CaloriesList.tsx

@@ -4,6 +4,8 @@ import { haveSameDay } from '../../utils/date';
 import { CaloriesInput } from '../caloriesInput/CaloriesInput';
 import { CalorieRow } from './CalorieRow';
 import styles from './caloriesList.css';
+import { ItemActions } from './ItemActions';
+import { ListEditBox } from './ListEditBox';
 
 function CaloriesList () {
   const {calorieItems = [], setCalorieItems, selectedDate} = useContext(AppContext);
@@ -21,6 +23,31 @@ function CaloriesList () {
     setCalorieItems(updatedCalories);
   }
 
+  function removeItem () {
+    setCalorieItems(calorieItems.filter((item) => item !== selectedItem));
+  }
+
+  function saveCalories (changedCalories: { title: string, count: number }) {
+    if (!selectedItem) {
+      return;
+    }
+
+    const itemIndex = calorieItems.indexOf(selectedItem);
+    const updatedItem = {
+      ...selectedItem,
+      ...changedCalories
+    };
+
+    const updatedItems =  [
+      ...calorieItems.slice(0, itemIndex),
+      updatedItem,
+      ...calorieItems.slice(itemIndex + 1)
+    ];
+
+    setCalorieItems(updatedItems);
+    setSelectedItem(updatedItem);
+  }
+
   function onRowClick (item: CalorieValue) {
     if (selectedItem === item) {
       return setSelectedItem(undefined);
@@ -36,13 +63,24 @@ function CaloriesList () {
       <section className={styles.caloriesItems}>
         {calories.map((item: CalorieValue, index: number) => {
           // todo: use better key
+          const isSelected = item === selectedItem;
           return (
-            <CalorieRow key={index} {...{item, onRowClick, isSelected: item === selectedItem}} />
+            <CalorieRow key={index} {...{item, onRowClick, isSelected}} />
           );
         })}
       </section>
 
-      <CaloriesInput {...{addCalories}} />
+      <div className={styles.editBoxes}>
+        {selectedItem &&
+        <ListEditBox>
+          <ItemActions {...{removeItem}} />
+        </ListEditBox>
+        }
+
+        <ListEditBox>
+          <CaloriesInput {...{addCalories, saveCalories, item: selectedItem}} />
+        </ListEditBox>
+      </div>
     </section>
   );
 }

+ 23 - 0
src/components/caloriesList/ItemActions.tsx

@@ -0,0 +1,23 @@
+import React from 'react';
+import { IconButton } from '../button/IconButton';
+import styles from './itemActions.css';
+
+type ItemActionsProps = {
+  removeItem: () => void;
+};
+
+function ItemActions (props: ItemActionsProps) {
+  const {
+    removeItem
+  } = props;
+
+  return (
+    <section className={styles.itemActions}>
+      <IconButton icon="trash" buttonClassName={styles.button} onClick={removeItem} />
+    </section>
+  );
+}
+
+export {
+  ItemActions
+};

+ 18 - 0
src/components/caloriesList/ListEditBox.tsx

@@ -0,0 +1,18 @@
+import React, { ReactNode } from 'react';
+import styles from './listEditBox.css';
+
+type ListEditBoxProps = {
+  children: ReactNode
+};
+
+function ListEditBox ({children}: ListEditBoxProps) {
+  return (
+    <div className={styles.listEditBox}>
+      {children}
+    </div>
+  );
+}
+
+export {
+  ListEditBox
+};

+ 4 - 0
src/components/caloriesList/caloriesList.css

@@ -7,3 +7,7 @@
 .caloriesItems {
     overflow: auto;
 }
+
+.editBoxes {
+    margin-top: auto;
+}

+ 11 - 0
src/components/caloriesList/itemActions.css

@@ -0,0 +1,11 @@
+.itemActions {
+    display: flex;
+    justify-content: flex-end;
+    font-size: 2em;
+
+    .button {
+        &:not(:first-child) {
+            margin-left: .3em;
+        }
+    }
+}

+ 4 - 0
src/components/caloriesList/listEditBox.css

@@ -0,0 +1,4 @@
+.listEditBox {
+    border-top: 1px solid #ccc;
+    padding: .5em;
+}

+ 1 - 1
src/components/icon/Icon.tsx

@@ -4,7 +4,7 @@ import styles from './icon.css';
 
 type IconProps = {
   icon: string,
-  className: string
+  className?: string
 };
 
 function Icon ({ icon, className }: IconProps) {

+ 21 - 0
src/components/icon/icon.css

@@ -1,6 +1,9 @@
 :root {
     --icon-settings: '\e900';
     --icon-plus: '\e901';
+    --icon-trash: '\e902';
+    --icon-edit: '\e903';
+    --icon-tick: '\e904';
 }
 
 .icon {
@@ -22,4 +25,22 @@
             content: var(--icon-plus);
         }
     }
+
+    &.icon-edit {
+        &:after {
+            content: var(--icon-edit);
+        }
+    }
+
+    &.icon-trash {
+        &:after {
+            content: var(--icon-trash);
+        }
+    }
+
+    &.icon-tick {
+        &:after {
+            content: var(--icon-tick);
+        }
+    }
 }