2 Komitmen a85bc7fe73 ... b5c72813fd

Pembuat SHA1 Pesan Tanggal
  Frédéric G. MARAND b5c72813fd Step 02: /contacts shows the index template, with working search. 6 bulan lalu
  Frédéric G. MARAND a85bc7fe73 Step 02 WIP: setting up for /contacts. 6 bulan lalu

+ 5 - 1
contactsapp/app.go

@@ -34,13 +34,14 @@ func contacts(cs *ContactsStore) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		search := r.URL.Query().Get("q")
 		var contacts_set []Contact
-		if search != "" {
+		if search == "" {
 			contacts_set = cs.GetAll()
 		} else {
 			contacts_set = cs.Get(search)
 		}
 		if err := tpl.ExecuteTemplate(w, "layout.html", data{
 			"contacts": contacts_set,
+			"search":   search,
 		}); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
@@ -59,6 +60,9 @@ func makeTemplate(first string, others ...string) *template.Template {
 
 func setupRoutes(mux *http.ServeMux, cs *ContactsStore) {
 	mux.HandleFunc("/", index)
+	mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
+		http.ServeFile(w, r, "./static/img/favicon.ico")
+	})
 	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
 	mux.Handle("/contacts", contacts(cs))
 }

+ 42 - 3
contactsapp/contacts_model.go

@@ -1,10 +1,13 @@
 package main
 
 import (
+	"cmp"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"os"
+	"slices"
+	"strings"
 	"sync"
 )
 
@@ -15,7 +18,7 @@ var (
 type (
 	Contacts      []Contact
 	ContactsStore struct {
-		sync.Mutex
+		sync.RWMutex
 		data map[int]Contact
 	}
 )
@@ -31,11 +34,46 @@ func NewContactsStore() (*ContactsStore, error) {
 }
 
 func (cs *ContactsStore) Get(search string) Contacts {
-	return make(Contacts, 0)
+	contacts := cs.GetAll()
+	if search == "" {
+		return contacts
+	}
+	return slices.DeleteFunc(contacts, func(c Contact) bool {
+		return (c.First == "" || !strings.Contains(c.First, search)) &&
+			(c.Last == "" || !strings.Contains(c.Last, search)) &&
+			(c.Email == "" || !strings.Contains(c.Email, search)) &&
+			(c.Phone == "" || !strings.Contains(c.Phone, search))
+	})
+}
+
+func MapValues[M ~map[K]V, K comparable, V any](m M) []V {
+	sl := make([]V, 0, len(m))
+	for _, v := range m {
+		sl = append(sl, v)
+	}
+	return sl
 }
 
 func (cs *ContactsStore) GetAll() Contacts {
-	return make(Contacts, 0)
+	cs.RLock()
+	defer cs.RUnlock()
+	contacts := MapValues(cs.data)
+	slices.SortStableFunc(contacts, func(a, b Contact) int {
+		if sgn := cmp.Compare(a.First, b.First); sgn != 0 {
+			return sgn
+		}
+		if sgn := cmp.Compare(a.Last, b.Last); sgn != 0 {
+			return sgn
+		}
+		if sgn := cmp.Compare(a.Phone, b.Phone); sgn != 0 {
+			return sgn
+		}
+		if sgn := cmp.Compare(a.Email, b.Email); sgn != 0 {
+			return sgn
+		}
+		return cmp.Compare(a.ID, b.ID)
+	})
+	return contacts
 }
 
 func (cs *ContactsStore) Load() error {
@@ -46,6 +84,7 @@ func (cs *ContactsStore) Load() error {
 	if err != nil {
 		return fmt.Errorf("reading file: %w", err)
 	}
+
 	contacts := make(Contacts, 0)
 	if err := json.Unmarshal(bs, &contacts); err != nil {
 		return fmt.Errorf("unmarshalling file: %w", err)

TEMPAT SAMPAH
contactsapp/static/img/favicon.ico


+ 10 - 0
contactsapp/static/site.css

@@ -63,3 +63,13 @@ td {
     tr:is(:hover, :focus-within) [data-overflow-menu] {
         visibility: visible;
     }
+
+all-caps {
+    text-transform: uppercase;
+}
+sub-title {
+    text-transform: capitalize;
+}
+a:link {
+    color: #5AA791;
+}

+ 34 - 1
contactsapp/templates/index.gohtml

@@ -1,5 +1,38 @@
 {{ define "index.gohtml" }}
     {{block "content" . }}
-      index
+      <form action="/contacts" method="get" class="tool-bar">
+        <label for="search">Search Term</label>
+        <input id="search" type="search" name="q" value="{{ .search }}"/>
+        <input type="submit" value="Search"/>
+      </form>
+
+      <table>
+        <thead>
+        <tr>
+          <th>First</th>
+          <th>Last</th>
+          <th>Phone</th>
+          <th>Email</th>
+          <th></th>
+        </tr>
+        </thead>
+        <tbody>
+        {{ range .contacts }}
+          <tr>
+            <td>{{ .First }}</td>
+            <td>{{ .Last }}</td>
+            <td>{{ .Phone }}</td>
+            <td>{{ .Email }}</td>
+            <td><a href="/contacts/{{ .ID }}/edit">Edit</a>
+              <a href="/contacts/{{ .ID }}">View</a></td>
+          </tr>
+        {{ else }}
+          <tr>
+            <td>No contacts founds.</td>
+          </tr>
+        {{ end }}
+        </tbody>
+      </table>
+      <p><a href="/contacts/new">Add Contact</a></p>
     {{ end}}
 {{ end }}

+ 11 - 6
contactsapp/templates/layout.gohtml

@@ -3,14 +3,19 @@
   <html lang="">
   <head>
     <title>Contact App</title>
-    <link rel="stylesheet" href="/static//missing.min.css">
+    <link rel="stylesheet" href="/static/missing.min.css">
     <link rel="stylesheet" href="/static/site.css">
   </head>
   <body>
-
-  {{ block "content" . }}
-    Default content
-  {{ end }}
+  <main>
+    <header>
+      <h1>
+        <all-caps>contacts.app</all-caps>
+        <sub-title>A demo contacts application</sub-title>
+      </h1>
+    </header>
+      {{ block "content" . }}Default content{{ end }}
+  </main>
   </body>
   </html>
-{{ end }}
+{{ end }}