-
Notifications
You must be signed in to change notification settings - Fork 8
1주차 과제 제출 - 고낙연 #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
18b3887
cf3ff6a
ae55cea
2d5afd1
36e128e
b1d4aab
b475289
34b04e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,287 @@ | ||||||||||||||||||
| const root = document.getElementById('root'); | ||||||||||||||||||
| if (root) { | ||||||||||||||||||
| root.innerHTML = '<p>Hello, world!</p>'; | ||||||||||||||||||
| /* @jsx createElement */ | ||||||||||||||||||
|
|
||||||||||||||||||
| declare namespace JSX { | ||||||||||||||||||
| interface IntrinsicElements { | ||||||||||||||||||
| [elemName: string]: any; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function capitalize(text: string) { | ||||||||||||||||||
| return text.charAt(0).toUpperCase() + text.slice(1); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function createElement( | ||||||||||||||||||
| type: string | Function, | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'Function' 타입 대신 명시적인 함수 타입을 사용하세요
function createElement(
- type: string | Function,
+ type: string | ((props: Record<string, unknown>) => Node),
props: any,
...children: any[]
) {📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (1.9.4)[error] 14-14: Don't use 'Function' as a type. Prefer explicitly define the function shape. This type accepts any function-like value, which can be a common source of bugs. (lint/complexity/noBannedTypes) |
||||||||||||||||||
| props: any, | ||||||||||||||||||
| ...children: any[] | ||||||||||||||||||
| ) { | ||||||||||||||||||
nakyeonko3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| if (typeof type === "function") { | ||||||||||||||||||
| return type({ ...props, children }); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const element = document.createElement(type); | ||||||||||||||||||
| Object.assign(element, props); | ||||||||||||||||||
| if (props) { | ||||||||||||||||||
| ["click", "submit"].forEach((event) => { | ||||||||||||||||||
| const handler = props[`on${capitalize(event)}`]; | ||||||||||||||||||
| if (handler) { | ||||||||||||||||||
| element.addEventListener(event, handler); | ||||||||||||||||||
| } | ||||||||||||||||||
| }); | ||||||||||||||||||
nakyeonko3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| } | ||||||||||||||||||
| children.forEach((child) => { | ||||||||||||||||||
| if (Array.isArray(child)) { | ||||||||||||||||||
| child.forEach((childItem) => element.append(childItem)); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
| element.append(child); | ||||||||||||||||||
| }); | ||||||||||||||||||
| return element; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| interface IComment { | ||||||||||||||||||
| id: number; | ||||||||||||||||||
| comment: string; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| interface Ticket { | ||||||||||||||||||
| id: number; | ||||||||||||||||||
| title: string; | ||||||||||||||||||
| description: string; | ||||||||||||||||||
| status: "open" | "closed"; | ||||||||||||||||||
| toggle(): void; | ||||||||||||||||||
| comments: IComment[]; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function Header() { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <header> | ||||||||||||||||||
| <h1>Tickets</h1> | ||||||||||||||||||
| </header> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function Main({ | ||||||||||||||||||
| tickets, | ||||||||||||||||||
| addTicket, | ||||||||||||||||||
| addComment, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| tickets: Ticket[]; | ||||||||||||||||||
| addTicket: ({ | ||||||||||||||||||
| title, | ||||||||||||||||||
| description, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| title: string; | ||||||||||||||||||
| description: string; | ||||||||||||||||||
| }) => void; | ||||||||||||||||||
| addComment: (comment: string, ticketId: number) => void; | ||||||||||||||||||
| }) { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <main> | ||||||||||||||||||
| <TicketForm addTicket={addTicket} /> | ||||||||||||||||||
| <TicketList tickets={tickets} addComment={addComment} /> | ||||||||||||||||||
| </main> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function CommentList({ comments }: { comments: IComment[] }) { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <div> | ||||||||||||||||||
| {comments.map((comment) => ( | ||||||||||||||||||
| <div className="comment-item" key={comment.id}> | ||||||||||||||||||
| {comment.comment} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ))} | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function CommentForm({ | ||||||||||||||||||
| addComment, | ||||||||||||||||||
| ticketId, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| addComment: (comment: string, ticketId: number) => void; | ||||||||||||||||||
| ticketId: number; | ||||||||||||||||||
| }) { | ||||||||||||||||||
| const handleAddComment = (event: Event) => { | ||||||||||||||||||
| event.preventDefault(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const form = event.target as HTMLFormElement; | ||||||||||||||||||
| const formData = new FormData(form); | ||||||||||||||||||
| const comment = formData.get("comment") as string; | ||||||||||||||||||
|
|
||||||||||||||||||
| addComment(comment, ticketId); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <form id="add-comment-form" onSubmit={handleAddComment}> | ||||||||||||||||||
| <div> | ||||||||||||||||||
| <label htmlFor="comment">Add a comment</label> | ||||||||||||||||||
| <input type="text" name="comment" id="comment" /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <button type="submit">Add Comment</button> | ||||||||||||||||||
| </form> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function TicketList({ | ||||||||||||||||||
| tickets, | ||||||||||||||||||
| addComment, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| tickets: Ticket[]; | ||||||||||||||||||
| addComment: (comment: string, ticketId: number) => void; | ||||||||||||||||||
| }) { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <ul id="ticket-list"> | ||||||||||||||||||
| {tickets.map((ticket) => ( | ||||||||||||||||||
| <TicketItem ticket={ticket} addComment={addComment} /> | ||||||||||||||||||
nakyeonko3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
| ))} | ||||||||||||||||||
| </ul> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function TicketItem({ | ||||||||||||||||||
| ticket, | ||||||||||||||||||
| addComment, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| ticket: Ticket; | ||||||||||||||||||
| addComment: (comment: string, ticketId: number) => void; | ||||||||||||||||||
| }) { | ||||||||||||||||||
| const handleClick = () => { | ||||||||||||||||||
| ticket.toggle(); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <li key={ticket.id}> | ||||||||||||||||||
| <div className="title">{ticket.title}</div> | ||||||||||||||||||
| <div className="description">{ticket.description}</div> | ||||||||||||||||||
| <button className="status" onClick={handleClick}> | ||||||||||||||||||
| {ticket.status === "open" ? "OPEN" : "CLOSED"} | ||||||||||||||||||
| </button> | ||||||||||||||||||
| <div id="comments"> | ||||||||||||||||||
| <CommentForm addComment={addComment} ticketId={ticket.id} /> | ||||||||||||||||||
| <CommentList comments={ticket.comments} /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </li> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function TicketForm({ | ||||||||||||||||||
| addTicket, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| addTicket: ({ | ||||||||||||||||||
| title, | ||||||||||||||||||
| description, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| title: string; | ||||||||||||||||||
| description: string; | ||||||||||||||||||
| }) => void; | ||||||||||||||||||
| }) { | ||||||||||||||||||
| const handleSubmit = (event: Event) => { | ||||||||||||||||||
| event.preventDefault(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const form = event.target as HTMLFormElement; | ||||||||||||||||||
| const formData = new FormData(form); | ||||||||||||||||||
| const title = formData.get("title") as string; | ||||||||||||||||||
| const description = formData.get("description") as string; | ||||||||||||||||||
|
|
||||||||||||||||||
| addTicket({ title, description }); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <form id="add-ticket-form" onSubmit={handleSubmit}> | ||||||||||||||||||
| <div> | ||||||||||||||||||
| <label for="ticket-title">Title</label> | ||||||||||||||||||
| <input type="text" name="title" id="ticket-title" placeholder="Title" /> | ||||||||||||||||||
|
Comment on lines
+194
to
+195
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HTML 레이블에 'for' 대신 'htmlFor' 속성을 사용하세요 JSX에서는 HTML의 - <label for="ticket-title">Title</label>
+ <label htmlFor="ticket-title">Title</label>
<input type="text" name="title" id="ticket-title" placeholder="Title" />📝 Committable suggestion
Suggested change
|
||||||||||||||||||
| </div> | ||||||||||||||||||
| <div> | ||||||||||||||||||
| <label for="ticket-description">Description</label> | ||||||||||||||||||
| <textarea | ||||||||||||||||||
|
Comment on lines
+198
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HTML 레이블에 'for' 대신 'htmlFor' 속성을 사용하세요 JSX에서는 HTML의 - <label for="ticket-description">Description</label>
+ <label htmlFor="ticket-description">Description</label>
<textarea📝 Committable suggestion
Suggested change
|
||||||||||||||||||
| name="description" | ||||||||||||||||||
| id="ticket-description" | ||||||||||||||||||
| placeholder="Description" | ||||||||||||||||||
| ></textarea> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <button type="submit" id="add-ticket"> | ||||||||||||||||||
| Add Ticket | ||||||||||||||||||
| </button> | ||||||||||||||||||
| </form> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function render({ | ||||||||||||||||||
| root, | ||||||||||||||||||
| tickets, | ||||||||||||||||||
| addTicket, | ||||||||||||||||||
| addComment, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| root: HTMLElement; | ||||||||||||||||||
| tickets: Ticket[]; | ||||||||||||||||||
| addTicket: ({ | ||||||||||||||||||
| title, | ||||||||||||||||||
| description, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| title: string; | ||||||||||||||||||
| description: string; | ||||||||||||||||||
| }) => void; | ||||||||||||||||||
| addComment: (comment: string, ticketId: number) => void; | ||||||||||||||||||
| }) { | ||||||||||||||||||
| root.replaceChildren( | ||||||||||||||||||
| <div> | ||||||||||||||||||
| <Header /> | ||||||||||||||||||
| <Main tickets={tickets} addTicket={addTicket} addComment={addComment} /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| document.addEventListener("DOMContentLoaded", () => { | ||||||||||||||||||
| const root = document.getElementById("root"); | ||||||||||||||||||
| if (root) { | ||||||||||||||||||
| const tickets: Ticket[] = []; | ||||||||||||||||||
|
|
||||||||||||||||||
| const update = () => { | ||||||||||||||||||
| render({ root, tickets, addTicket, addComment }); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const addTicket = ({ | ||||||||||||||||||
| title, | ||||||||||||||||||
| description, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| title: string; | ||||||||||||||||||
| description: string; | ||||||||||||||||||
| }) => { | ||||||||||||||||||
| const id = Math.max(...tickets.map((ticket) => ticket.id), 0) + 1; | ||||||||||||||||||
|
|
||||||||||||||||||
| const ticket: Ticket = { | ||||||||||||||||||
| id, | ||||||||||||||||||
| title, | ||||||||||||||||||
| description, | ||||||||||||||||||
| status: "open", | ||||||||||||||||||
| toggle() { | ||||||||||||||||||
| this.status = this.status === "open" ? "closed" : "open"; | ||||||||||||||||||
| update(); | ||||||||||||||||||
| }, | ||||||||||||||||||
| comments: [], | ||||||||||||||||||
| }; | ||||||||||||||||||
nakyeonko3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| tickets.push(ticket); | ||||||||||||||||||
| update(); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const addComment = (comment: string, ticketId: number) => { | ||||||||||||||||||
| const ticket = tickets.find((ticket) => ticket.id === ticketId); | ||||||||||||||||||
| if (ticket) { | ||||||||||||||||||
| ticket.comments = [ | ||||||||||||||||||
| ...ticket.comments, | ||||||||||||||||||
| { | ||||||||||||||||||
| id: Math.max(...ticket.comments.map((c) => c.id), 0) + 1, | ||||||||||||||||||
| comment, | ||||||||||||||||||
| }, | ||||||||||||||||||
| ]; | ||||||||||||||||||
| update(); | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| update(); | ||||||||||||||||||
| } | ||||||||||||||||||
| }); | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| html { | ||
| box-sizing: border-box; | ||
| } | ||
|
|
||
| *, | ||
| *::before, | ||
| *::after { | ||
| box-sizing: inherit; | ||
| } | ||
|
|
||
| html { | ||
| font-size: 62.5%; | ||
| } | ||
|
|
||
| body { | ||
| font-size: 1.6rem; | ||
| } | ||
|
|
||
| #ticket-list { | ||
| margin: 0; | ||
| padding: 0; | ||
| list-style: none; | ||
|
|
||
| li { | ||
| margin-block: 1rem; | ||
| padding: 1rem; | ||
| border: 1px solid #ccc; | ||
|
|
||
| .title { | ||
| font-size: 2rem; | ||
| font-weight: 700; | ||
| } | ||
|
|
||
| .description { | ||
| margin-block: 0.5rem; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| form { | ||
| margin-block: 2rem; | ||
| max-width: 40rem; | ||
|
|
||
| div { | ||
| margin-block: 1rem; | ||
| } | ||
|
|
||
| label { | ||
| display: block; | ||
| margin-bottom: 0.4rem; | ||
| } | ||
|
|
||
| input, | ||
| textarea { | ||
| width: 100%; | ||
| padding: 0.8rem 1rem; | ||
| } | ||
|
|
||
| textarea { | ||
| height: 10rem; | ||
| } | ||
|
|
||
| button { | ||
| width: 100%; | ||
| padding: 0.8rem 1rem; | ||
| } | ||
| } | ||
|
|
||
| #comments { | ||
| margin-block: 1rem; | ||
| } | ||
|
|
||
| .comment-item { | ||
| margin-block: 0.5rem; | ||
| padding: 0.8rem 1rem; | ||
| border: 1px solid #ccc; | ||
| border-radius: 4px; | ||
| } | ||
|
|
||
| .status { | ||
| margin-block: 0.5rem; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.