panku-app/src/components/activity-form.jsx

1import { useState, useMemo } from "react"; 2import { makeId } from "@/utils"; 3 4const getDefaultValues = (activity) => { 5 const defaultValue = { 6 activityTitle: "", 7 itemSpends: [ 8 { 9 id: makeId(), 10 title: "", 11 amount: 10, 12 }, 13 ], 14 selectedFriends: [], 15 isComplete: false, 16 }; 17 if (!activity) { 18 return defaultValue; 19 } 20 21 defaultValue.activityTitle = activity.title || ""; 22 defaultValue.itemSpends = activity.itemSpends || []; 23 defaultValue.selectedFriends = activity.friends?.map((f) => f.id) || []; 24 defaultValue.isComplete = activity.isCompleted || false; 25 26 return defaultValue; 27}; 28 29const ActivityForm = (props) => { 30 const { friends, onSubmit, formInfo } = props; 31 const defaultValues = getDefaultValues(formInfo.defaultValues); 32 const isEdit = formInfo.type === "edit"; 33 const isDisabled = defaultValues.isComplete; 34 35 const [selectedFriends, setSelectedFriends] = useState( 36 defaultValues.selectedFriends 37 ); // array of ids 38 const [activityTitle, setActivityTitle] = useState( 39 defaultValues.activityTitle 40 ); 41 const [itemSpends, setItemSpends] = useState(defaultValues.itemSpends); 42 const [isComplete, setIsComplete] = useState(defaultValues.isComplete); 43 44 const totalAmount = useMemo(() => { 45 let total = 0; 46 let splitPerPerson = 0; 47 for (const item of itemSpends) { 48 if (item["amount"] !== "") { 49 total += item["amount"]; 50 } 51 } 52 53 if (total > 0 && selectedFriends.length) { 54 const totalPeople = 1 + selectedFriends.length; // me + friends 55 splitPerPerson = Math.round(total / totalPeople); 56 } 57 58 return { total, splitPerPerson }; 59 }, [itemSpends, selectedFriends]); 60 61 const updateItemSpend = (idx, key, newValue) => { 62 setItemSpends((prev) => { 63 const _itemSpends = [...prev]; 64 _itemSpends[idx] = { 65 ..._itemSpends[idx], 66 [key]: newValue, 67 }; 68 return _itemSpends; 69 }); 70 }; 71 72 const addNewItemSpend = () => { 73 setItemSpends((prev) => { 74 const _itemSpends = [...prev]; 75 _itemSpends.push({ 76 id: makeId(), 77 title: "", 78 amount: 10, 79 }); 80 return _itemSpends; 81 }); 82 }; 83 84 const removeItemSpend = (idx) => { 85 setItemSpends((prev) => { 86 const _itemSpends = [...prev]; 87 _itemSpends.splice(idx, 1); 88 return _itemSpends; 89 }); 90 }; 91 92 const handleSelectionChange = (event) => { 93 const options = event.target.options; 94 const values = []; 95 for (let i = 0; i < options.length; i++) { 96 if (options[i].selected) { 97 values.push(options[i].value); 98 } 99 } 100 setSelectedFriends(values); 101 }; 102 103 function handleSubmit(e) { 104 e.preventDefault(); 105 106 const activity = { 107 id: makeId(), 108 title: activityTitle, 109 itemSpends, 110 friends: friends.filter((f) => selectedFriends.includes(f.id)), 111 totalAmount: totalAmount.total, 112 splitPerPerson: totalAmount.splitPerPerson, 113 createdAt: new Date().toISOString(), 114 isCompleted: isComplete, 115 }; 116 117 if (isEdit) { 118 delete activity["id"]; 119 delete activity["createdAt"]; 120 } 121 122 onSubmit(activity); 123 return; 124 } 125 126 return ( 127 <form onSubmit={handleSubmit}> 128 <label htmlFor="title">what was the activity?</label> 129 <input 130 type="text" 131 name="title" 132 id="title" 133 required 134 placeholder="Trip to the mansion" 135 value={activityTitle} 136 onChange={(e) => setActivityTitle(e.target.value)} 137 disabled={isDisabled} 138 /> 139 140 <fieldset disabled={isDisabled}> 141 <legend>items and spends</legend> 142 <ol style={{ paddingLeft: "22px" }}> 143 {itemSpends.map((item, idx) => ( 144 <li key={item.id}> 145 <input 146 type="text" 147 placeholder="Food" 148 required 149 value={item.title} 150 onChange={(e) => updateItemSpend(idx, "title", e.target.value)} 151 /> 152 <input 153 type="number" 154 placeholder="250" 155 style={{ marginTop: "3px" }} 156 required 157 min={1} 158 value={item.amount} 159 onChange={(e) => { 160 let val = e.target.value; 161 if (val !== "") { 162 val = parseInt(val); 163 } 164 updateItemSpend(idx, "amount", val); 165 }} 166 /> 167 {/* cant remove all spends */} 168 {itemSpends.length > 1 && ( 169 <button 170 className="btn btn-text" 171 type="button" 172 onClick={() => removeItemSpend(idx)} 173 disabled={isDisabled} 174 > 175 &minus; remove 176 </button> 177 )} 178 </li> 179 ))} 180 </ol> 181 182 <div className="text-right"> 183 <button 184 className="btn" 185 type="button" 186 onClick={addNewItemSpend} 187 disabled={isDisabled} 188 > 189 &#43; add 190 </button> 191 </div> 192 193 <div> 194 <p> 195 <small>total:</small> <strong>₹{totalAmount.total}</strong> 196 </p> 197 </div> 198 </fieldset> 199 200 <fieldset disabled={isDisabled}> 201 <legend>friends</legend> 202 203 <select 204 name="friends" 205 id="friends" 206 multiple={true} 207 className="multi-select" 208 required 209 value={selectedFriends} 210 onChange={handleSelectionChange} 211 > 212 {friends.map((f) => ( 213 <option value={f.id} key={f.id}> 214 {f.name} 215 </option> 216 ))} 217 </select> 218 <span className="muted"> 219 <small>{selectedFriends.length} selected &#43; me</small> 220 </span> 221 </fieldset> 222 223 <div className="text-center"> 224 <p> 225 <small>per person:</small>{" "} 226 <strong>₹{totalAmount.splitPerPerson}</strong> 227 </p> 228 </div> 229 230 {isEdit && ( 231 <div className="info-box"> 232 <label> 233 <input 234 type="checkbox" 235 checked={isComplete} 236 onChange={(e) => { 237 setIsComplete(e.target.checked); 238 }} 239 disabled={isDisabled} 240 /> 241 This activity is now complete. 242 </label> 243 </div> 244 )} 245 246 <div className="text-center"> 247 <button className="btn" type="submit" disabled={isDisabled}> 248 {isEdit ? "update" : "add"} this activity 249 </button> 250 </div> 251 </form> 252 ); 253}; 254 255export default ActivityForm;