180 lines
5.2 KiB
JavaScript
180 lines
5.2 KiB
JavaScript
function App() {
|
|
const { Container, Row, Col } = ReactBootstrap;
|
|
return (
|
|
<Container>
|
|
<Row>
|
|
<Col md={{ offset: 3, span: 6 }}>
|
|
<TodoListCard />
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
function TodoListCard() {
|
|
const [items, setItems] = React.useState(null);
|
|
|
|
React.useEffect(() => {
|
|
fetch('/items')
|
|
.then(r => r.json())
|
|
.then(setItems);
|
|
}, []);
|
|
|
|
const onNewItem = React.useCallback(
|
|
newItem => {
|
|
setItems([...items, newItem]);
|
|
},
|
|
[items],
|
|
);
|
|
|
|
const onItemUpdate = React.useCallback(
|
|
item => {
|
|
const index = items.findIndex(i => i.id === item.id);
|
|
setItems([
|
|
...items.slice(0, index),
|
|
item,
|
|
...items.slice(index + 1),
|
|
]);
|
|
},
|
|
[items],
|
|
);
|
|
|
|
const onItemRemoval = React.useCallback(
|
|
item => {
|
|
const index = items.findIndex(i => i.id === item.id);
|
|
setItems([...items.slice(0, index), ...items.slice(index + 1)]);
|
|
},
|
|
[items],
|
|
);
|
|
|
|
if (items === null) return 'Loading...';
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<AddItemForm onNewItem={onNewItem} />
|
|
{items.length === 0 && (
|
|
<p className="text-center">Adicionar um item</p>
|
|
)}
|
|
{items.map(item => (
|
|
<ItemDisplay
|
|
item={item}
|
|
key={item.id}
|
|
onItemUpdate={onItemUpdate}
|
|
onItemRemoval={onItemRemoval}
|
|
/>
|
|
))}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
|
|
function AddItemForm({ onNewItem }) {
|
|
const { Form, InputGroup, Button } = ReactBootstrap;
|
|
|
|
const [newItem, setNewItem] = React.useState('');
|
|
const [submitting, setSubmitting] = React.useState(false);
|
|
|
|
const submitNewItem = e => {
|
|
e.preventDefault();
|
|
setSubmitting(true);
|
|
fetch('/items', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ name: newItem }),
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
.then(r => r.json())
|
|
.then(item => {
|
|
onNewItem(item);
|
|
setSubmitting(false);
|
|
setNewItem('');
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Form onSubmit={submitNewItem}>
|
|
<InputGroup className="mb-3">
|
|
<Form.Control
|
|
value={newItem}
|
|
onChange={e => setNewItem(e.target.value)}
|
|
type="text"
|
|
placeholder="Novo produto"
|
|
aria-describedby="basic-addon1"
|
|
/>
|
|
<InputGroup.Append>
|
|
<Button
|
|
type="submit"
|
|
variant="success"
|
|
disabled={!newItem.length}
|
|
className={submitting ? 'disabled' : ''}
|
|
>
|
|
{submitting ? 'Adding...' : 'OK'}
|
|
</Button>
|
|
</InputGroup.Append>
|
|
</InputGroup>
|
|
</Form>
|
|
);
|
|
}
|
|
|
|
function ItemDisplay({ item, onItemUpdate, onItemRemoval }) {
|
|
const { Container, Row, Col, Button } = ReactBootstrap;
|
|
|
|
const toggleCompletion = () => {
|
|
fetch(`/items/${item.id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
name: item.name,
|
|
completed: !item.completed,
|
|
}),
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
.then(r => r.json())
|
|
.then(onItemUpdate);
|
|
};
|
|
|
|
const removeItem = () => {
|
|
fetch(`/items/${item.id}`, { method: 'DELETE' }).then(() =>
|
|
onItemRemoval(item),
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Container fluid className={`item ${item.completed && 'completed'}`}>
|
|
<Row>
|
|
<Col xs={1} className="text-center">
|
|
<Button
|
|
className="toggles"
|
|
size="sm"
|
|
variant="link"
|
|
onClick={toggleCompletion}
|
|
aria-label={
|
|
item.completed
|
|
? 'Mark item as incomplete'
|
|
: 'Mark item as complete'
|
|
}
|
|
>
|
|
<i
|
|
className={`far ${
|
|
item.completed ? 'fa-check-square' : 'fa-square'
|
|
}`}
|
|
/>
|
|
</Button>
|
|
</Col>
|
|
<Col xs={10} className="name">
|
|
{item.name}
|
|
</Col>
|
|
<Col xs={1} className="text-center remove">
|
|
<Button
|
|
size="sm"
|
|
variant="link"
|
|
onClick={removeItem}
|
|
aria-label="Remove Item"
|
|
>
|
|
<i className="fa fa-trash text-danger" />
|
|
</Button>
|
|
</Col>
|
|
</Row>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
ReactDOM.render(<App />, document.getElementById('root'));
|