Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee
17
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: styling improvements

pdewey 530c8dd5 66d28436

+130 -64
+6
internal/atproto/records.go
··· 379 379 if brewer.Description != "" { 380 380 record["description"] = brewer.Description 381 381 } 382 + if brewer.BrewerType != "" { 383 + record["brewerType"] = brewer.BrewerType 384 + } 382 385 383 386 return record, nil 384 387 } ··· 417 420 // Optional fields 418 421 if description, ok := record["description"].(string); ok { 419 422 brewer.Description = description 423 + } 424 + if brewerType, ok := record["brewerType"].(string); ok { 425 + brewer.BrewerType = brewerType 420 426 } 421 427 422 428 return brewer, nil
+4
internal/atproto/records_test.go
··· 639 639 t.Run("brewer round trip", func(t *testing.T) { 640 640 original := &models.Brewer{ 641 641 Name: "Hario V60", 642 + BrewerType: "Pour-Over", 642 643 Description: "Pour-over dripper", 643 644 CreatedAt: createdAt, 644 645 } ··· 655 656 656 657 if restored.Name != original.Name { 657 658 t.Errorf("Name = %v, want %v", restored.Name, original.Name) 659 + } 660 + if restored.BrewerType != original.BrewerType { 661 + t.Errorf("BrewerType = %v, want %v", restored.BrewerType, original.BrewerType) 658 662 } 659 663 if restored.Description != original.Description { 660 664 t.Errorf("Description = %v, want %v", restored.Description, original.Description)
+2
internal/atproto/store.go
··· 845 845 func (s *AtprotoStore) CreateBrewer(ctx context.Context, brewer *models.CreateBrewerRequest) (*models.Brewer, error) { 846 846 brewerModel := &models.Brewer{ 847 847 Name: brewer.Name, 848 + BrewerType: brewer.BrewerType, 848 849 Description: brewer.Description, 849 850 CreatedAt: time.Now(), 850 851 } ··· 942 943 943 944 brewerModel := &models.Brewer{ 944 945 Name: brewer.Name, 946 + BrewerType: brewer.BrewerType, 945 947 Description: brewer.Description, 946 948 CreatedAt: existing.CreatedAt, 947 949 }
+5 -5
internal/feed/service.go
··· 316 316 for _, brew := range result.brews { 317 317 items = append(items, &FeedItem{ 318 318 RecordType: "brew", 319 - Action: "added a new brew", 319 + Action: "☕ added a new brew", 320 320 Brew: brew, 321 321 Author: result.profile, 322 322 Timestamp: brew.CreatedAt, ··· 328 328 for _, bean := range result.beans { 329 329 items = append(items, &FeedItem{ 330 330 RecordType: "bean", 331 - Action: "added a new bean", 331 + Action: "🫘 added a new bean", 332 332 Bean: bean, 333 333 Author: result.profile, 334 334 Timestamp: bean.CreatedAt, ··· 340 340 for _, roaster := range result.roasters { 341 341 items = append(items, &FeedItem{ 342 342 RecordType: "roaster", 343 - Action: "added a new roaster", 343 + Action: "🏪 added a new roaster", 344 344 Roaster: roaster, 345 345 Author: result.profile, 346 346 Timestamp: roaster.CreatedAt, ··· 352 352 for _, grinder := range result.grinders { 353 353 items = append(items, &FeedItem{ 354 354 RecordType: "grinder", 355 - Action: "added a new grinder", 355 + Action: "⚙️ added a new grinder", 356 356 Grinder: grinder, 357 357 Author: result.profile, 358 358 Timestamp: grinder.CreatedAt, ··· 364 364 for _, brewer := range result.brewers { 365 365 items = append(items, &FeedItem{ 366 366 RecordType: "brewer", 367 - Action: "added a new brewer", 367 + Action: "☕ added a new brewer", 368 368 Brewer: brewer, 369 369 Author: result.profile, 370 370 Timestamp: brewer.CreatedAt,
+10
internal/models/models.go
··· 19 19 MaxGrindSizeLength = 100 20 20 MaxGrinderTypeLength = 50 21 21 MaxBurrTypeLength = 50 22 + MaxBrewerTypeLength = 100 22 23 ) 23 24 24 25 // Validation errors ··· 67 68 type Brewer struct { 68 69 RKey string `json:"rkey"` // Record key 69 70 Name string `json:"name"` 71 + BrewerType string `json:"brewer_type"` 70 72 Description string `json:"description"` 71 73 CreatedAt time.Time `json:"created_at"` 72 74 } ··· 144 146 145 147 type CreateBrewerRequest struct { 146 148 Name string `json:"name"` 149 + BrewerType string `json:"brewer_type"` 147 150 Description string `json:"description"` 148 151 } 149 152 ··· 171 174 172 175 type UpdateBrewerRequest struct { 173 176 Name string `json:"name"` 177 + BrewerType string `json:"brewer_type"` 174 178 Description string `json:"description"` 175 179 } 176 180 ··· 302 306 if len(r.Name) > MaxNameLength { 303 307 return ErrNameTooLong 304 308 } 309 + if len(r.BrewerType) > MaxBrewerTypeLength { 310 + return ErrFieldTooLong 311 + } 305 312 if len(r.Description) > MaxDescriptionLength { 306 313 return ErrDescTooLong 307 314 } ··· 315 322 } 316 323 if len(r.Name) > MaxNameLength { 317 324 return ErrNameTooLong 325 + } 326 + if len(r.BrewerType) > MaxBrewerTypeLength { 327 + return ErrFieldTooLong 318 328 } 319 329 if len(r.Description) > MaxDescriptionLength { 320 330 return ErrDescTooLong
+5
lexicons/social.arabica.alpha.brewer.json
··· 15 15 "maxLength": 200, 16 16 "description": "Name of the brewer (e.g., 'V60', 'Aeropress', 'Chemex')" 17 17 }, 18 + "brewerType": { 19 + "type": "string", 20 + "maxLength": 100, 21 + "description": "Type of brewer (e.g., 'Pour-Over', 'Immersion', 'Espresso Machine', 'Lever Espresso Machine', 'Moka Pot', 'French Press')" 22 + }, 18 23 "description": { 19 24 "type": "string", 20 25 "maxLength": 1000,
+4 -4
templates/partials/brew_list_content.tmpl
··· 41 41 <span class="font-medium">{{.Bean.Roaster.Name}}</span> 42 42 </div> 43 43 {{end}} 44 - <div class="text-xs text-brown-600 mt-0.5 space-y-0.5"> 45 - {{if .Bean.Origin}}<div>📍 {{.Bean.Origin}}</div>{{end}} 46 - {{if .Bean.RoastLevel}}<div>🔥 {{.Bean.RoastLevel}}</div>{{end}} 47 - {{if hasValue .CoffeeAmount}}<div>⚖️ {{.CoffeeAmount}}g</div>{{end}} 44 + <div class="text-xs text-brown-600 mt-0.5 flex flex-wrap gap-x-2 gap-y-0.5"> 45 + {{if .Bean.Origin}}<span class="inline-flex items-center gap-0.5">📍 {{.Bean.Origin}}</span>{{end}} 46 + {{if .Bean.RoastLevel}}<span class="inline-flex items-center gap-0.5">🔥 {{.Bean.RoastLevel}}</span>{{end}} 47 + {{if hasValue .CoffeeAmount}}<span class="inline-flex items-center gap-0.5">⚖️ {{.CoffeeAmount}}g</span>{{end}} 48 48 </div> 49 49 {{else}} 50 50 <span class="text-brown-400">-</span>
+43 -27
templates/partials/feed.tmpl
··· 33 33 </div> 34 34 35 35 <!-- Action header --> 36 - <div class="mb-2 text-sm text-brown-700 italic"> 36 + <div class="mb-2 text-sm text-brown-700"> 37 37 {{.Action}} 38 38 </div> 39 39 40 40 <!-- Record content --> 41 41 {{if eq .RecordType "brew"}} 42 42 <!-- Brew info --> 43 - <div class="bg-white/60 backdrop-blur rounded-lg p-3 border border-brown-200"> 44 - <!-- Bean and roaster info --> 45 - <div class="flex items-start justify-between gap-2 mb-2"> 43 + <div class="bg-white/60 backdrop-blur rounded-lg p-4 border border-brown-200"> 44 + <!-- Bean info with rating --> 45 + <div class="flex items-start justify-between gap-3 mb-3"> 46 46 <div class="flex-1 min-w-0"> 47 47 {{if .Brew.Bean}} 48 - <!-- Bean name and roaster --> 49 - <div class="text-base"> 50 - <span class="text-brown-700">Bean:</span> 51 - <span class="font-bold text-brown-900"> 52 - {{if .Brew.Bean.Name}}{{.Brew.Bean.Name}}{{else}}{{.Brew.Bean.Origin}}{{end}}{{if and .Brew.Bean.Roaster .Brew.Bean.Roaster.Name}} - {{.Brew.Bean.Roaster.Name}}{{end}} 53 - </span> 48 + <div class="font-bold text-brown-900 text-base"> 49 + {{if .Brew.Bean.Name}}{{.Brew.Bean.Name}}{{else}}{{.Brew.Bean.Origin}}{{end}} 50 + </div> 51 + {{if and .Brew.Bean.Roaster .Brew.Bean.Roaster.Name}} 52 + <div class="text-sm text-brown-700 mt-0.5"> 53 + <span class="font-medium">🏪 {{.Brew.Bean.Roaster.Name}}</span> 54 54 </div> 55 - <!-- Process, roast level, origin --> 56 - <div class="text-sm text-brown-600"> 57 - {{if .Brew.Bean.Process}}{{.Brew.Bean.Process}}{{end}}{{if and .Brew.Bean.Process .Brew.Bean.RoastLevel}} • {{end}}{{if .Brew.Bean.RoastLevel}}{{.Brew.Bean.RoastLevel}}{{end}}{{if and (or .Brew.Bean.Process .Brew.Bean.RoastLevel) .Brew.Bean.Origin}} • {{end}}{{if .Brew.Bean.Origin}}{{.Brew.Bean.Origin}}{{end}} 55 + {{end}} 56 + <div class="text-xs text-brown-600 mt-1 flex flex-wrap gap-x-2 gap-y-0.5"> 57 + {{if .Brew.Bean.Origin}}<span class="inline-flex items-center gap-0.5">📍 {{.Brew.Bean.Origin}}</span>{{end}} 58 + {{if .Brew.Bean.RoastLevel}}<span class="inline-flex items-center gap-0.5">🔥 {{.Brew.Bean.RoastLevel}}</span>{{end}} 59 + {{if .Brew.Bean.Process}}<span class="inline-flex items-center gap-0.5">🌱 {{.Brew.Bean.Process}}</span>{{end}} 60 + {{if hasValue .Brew.CoffeeAmount}}<span class="inline-flex items-center gap-0.5">⚖️ {{.Brew.CoffeeAmount}}g</span>{{end}} 58 61 </div> 59 62 {{end}} 60 63 </div> 61 64 {{if hasValue .Brew.Rating}} 62 - <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 text-amber-900 flex-shrink-0"> 65 + <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-amber-100 text-amber-900 flex-shrink-0"> 63 66 ⭐ {{.Brew.Rating}}/10 64 67 </span> 65 68 {{end}} 66 69 </div> 67 70 68 - <!-- Brew parameters --> 69 - <div class="grid grid-cols-2 gap-2 text-sm text-brown-700"> 70 - {{if hasValue .Brew.CoffeeAmount}} 71 + <!-- Brewer --> 72 + {{if or .Brew.BrewerObj .Brew.Method}} 73 + <div class="mb-2"> 74 + <span class="text-xs text-brown-600">Brewer:</span> 75 + <span class="text-sm font-semibold text-brown-900"> 76 + {{if .Brew.BrewerObj}}{{.Brew.BrewerObj.Name}}{{else if .Brew.Method}}{{.Brew.Method}}{{end}} 77 + </span> 78 + </div> 79 + {{end}} 80 + 81 + <!-- Brew parameters in compact grid --> 82 + <div class="grid grid-cols-2 gap-x-4 gap-y-1 text-xs text-brown-700"> 83 + {{if .Brew.GrinderObj}} 71 84 <div> 72 - <span class="text-brown-600">Coffee:</span> {{.Brew.CoffeeAmount}}g 85 + <span class="text-brown-600">Grinder:</span> {{.Brew.GrinderObj.Name}}{{if .Brew.GrindSize}} ({{.Brew.GrindSize}}){{end}} 86 + </div> 87 + {{else if .Brew.GrindSize}} 88 + <div> 89 + <span class="text-brown-600">Grind:</span> {{.Brew.GrindSize}} 73 90 </div> 74 91 {{end}} 75 - {{if hasValue .Brew.WaterAmount}} 92 + {{if .Brew.Pours}} 93 + <div class="col-span-2"> 94 + <span class="text-brown-600">Pours:</span> 95 + {{range .Brew.Pours}} 96 + <div class="pl-2 text-brown-600">• {{.WaterAmount}}g @ {{formatTime .TimeSeconds}}</div> 97 + {{end}} 98 + </div> 99 + {{else if hasValue .Brew.WaterAmount}} 76 100 <div> 77 101 <span class="text-brown-600">Water:</span> {{.Brew.WaterAmount}}g 78 102 </div> ··· 87 111 <span class="text-brown-600">Time:</span> {{formatTime .Brew.TimeSeconds}} 88 112 </div> 89 113 {{end}} 90 - {{if .Brew.GrindSize}} 91 - <div> 92 - <span class="text-brown-600">Grind:</span> {{.Brew.GrindSize}}{{if .Brew.GrinderObj}} ({{.Brew.GrinderObj.Name}}){{end}} 93 - </div> 94 - {{end}} 95 - <div> 96 - <span class="text-brown-600">Brewer:</span> <span class="font-bold text-brown-900">{{if .Brew.BrewerObj}}{{.Brew.BrewerObj.Name}}{{else if .Brew.Method}}{{.Brew.Method}}{{else}}-{{end}}</span> 97 - </div> 98 114 </div> 99 115 100 116 {{if .Brew.TastingNotes}}
+25 -15
templates/partials/manage_content.tmpl
··· 19 19 <table class="min-w-full divide-y divide-brown-300"> 20 20 <thead class="bg-brown-200/80"> 21 21 <tr> 22 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">Name</th> 23 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">📍 Origin</th> 24 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">☕ Roaster</th> 25 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">🔥 Roast Level</th> 26 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">🌱 Process</th> 27 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">📝 Description</th> 28 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">Actions</th> 22 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">Name</th> 23 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">📍 Origin</th> 24 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">☕ Roaster</th> 25 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">🔥 Roast Level</th> 26 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">🌱 Process</th> 27 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">📝 Description</th> 28 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase whitespace-nowrap">Actions</th> 29 29 </tr> 30 30 </thead> 31 31 <tbody class="bg-brown-50/60 divide-y divide-brown-200"> ··· 160 160 <div x-show="tab === 'brewers'"> 161 161 <div class="mb-4 flex justify-between items-center"> 162 162 <h3 class="text-xl font-semibold text-brown-900">Brewers</h3> 163 - <button @click="showBrewerForm = true; editingBrewer = null; brewerForm = {name: '', description: ''}" 163 + <button @click="showBrewerForm = true; editingBrewer = null; brewerForm = {name: '', brewer_type: '', description: ''}" 164 164 class="bg-gradient-to-r from-brown-700 to-brown-800 text-white px-4 py-2 rounded-lg hover:from-brown-800 hover:to-brown-900 transition-all font-medium shadow-md hover:shadow-lg"> 165 165 + Add Brewer 166 166 </button> ··· 176 176 <thead class="bg-brown-200/80"> 177 177 <tr> 178 178 <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">Name</th> 179 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">🔧 Type</th> 179 180 <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">📝 Description</th> 180 181 <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase">Actions</th> 181 182 </tr> 182 183 </thead> 183 184 <tbody class="bg-brown-50/60 divide-y divide-brown-200"> 184 185 {{range .Brewers}} 185 - <tr class="hover:bg-brown-100/60 transition-colors"> 186 + <tr class="hover:bg-brown-100/60 transition-colors" 187 + data-rkey="{{.RKey}}" 188 + data-name="{{.Name}}" 189 + data-brewer-type="{{.BrewerType}}" 190 + data-description="{{.Description}}"> 186 191 <td class="px-6 py-4 text-sm font-medium text-brown-900">{{.Name}}</td> 192 + <td class="px-6 py-4 text-sm text-brown-900"> 193 + {{if .BrewerType}}{{.BrewerType}}{{else}}<span class="text-brown-400">-</span>{{end}} 194 + </td> 187 195 <td class="px-6 py-4 text-sm text-brown-700">{{.Description}}</td> 188 196 <td class="px-6 py-4 text-sm font-medium space-x-2"> 189 - <button @click="editBrewer('{{.RKey}}', '{{.Name}}', '{{.Description}}')" 197 + <button @click="editBrewerFromRow($el.closest('tr'))" 190 198 class="text-brown-700 hover:text-brown-900 font-medium">Edit</button> 191 - <button @click="deleteBrewer('{{.RKey}}')" 199 + <button @click="deleteBrewer($el.closest('tr').dataset.rkey)" 192 200 class="text-brown-600 hover:text-brown-800 font-medium">Delete</button> 193 201 </td> 194 202 </tr> ··· 200 208 </div> 201 209 202 210 <!-- Bean Form Modal --> 203 - <div x-cloak x-show="showBeanForm" class="fixed inset-0 bg-brown-900/75 flex items-center justify-center z-50"> 211 + <div x-cloak x-show="showBeanForm" class="fixed inset-0 bg-black/40 flex items-center justify-center z-50"> 204 212 <div class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl border-2 border-brown-300 p-8 max-w-md w-full mx-4 shadow-2xl"> 205 213 <h3 class="text-xl font-semibold mb-4 text-brown-900" x-text="editingBean ? 'Edit Bean' : 'Add Bean'"></h3> 206 214 <div class="space-y-4"> ··· 238 246 </div> 239 247 240 248 <!-- Roaster Form Modal --> 241 - <div x-cloak x-show="showRoasterForm" class="fixed inset-0 bg-brown-900/75 flex items-center justify-center z-50"> 249 + <div x-cloak x-show="showRoasterForm" class="fixed inset-0 bg-black/40 flex items-center justify-center z-50"> 242 250 <div class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl border-2 border-brown-300 p-8 max-w-md w-full mx-4 shadow-2xl"> 243 251 <h3 class="text-xl font-semibold mb-4 text-brown-900" x-text="editingRoaster ? 'Edit Roaster' : 'Add Roaster'"></h3> 244 252 <div class="space-y-4"> ··· 259 267 </div> 260 268 261 269 <!-- Grinder Form Modal --> 262 - <div x-cloak x-show="showGrinderForm" class="fixed inset-0 bg-brown-900/75 flex items-center justify-center z-50"> 270 + <div x-cloak x-show="showGrinderForm" class="fixed inset-0 bg-black/40 flex items-center justify-center z-50"> 263 271 <div class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl border-2 border-brown-300 p-8 max-w-md w-full mx-4 shadow-2xl"> 264 272 <h3 class="text-xl font-semibold mb-4 text-brown-900" x-text="editingGrinder ? 'Edit Grinder' : 'Add Grinder'"></h3> 265 273 <div class="space-y-4"> ··· 289 297 </div> 290 298 291 299 <!-- Brewer Form Modal --> 292 - <div x-cloak x-show="showBrewerForm" class="fixed inset-0 bg-brown-900/75 flex items-center justify-center z-50"> 300 + <div x-cloak x-show="showBrewerForm" class="fixed inset-0 bg-black/40 flex items-center justify-center z-50"> 293 301 <div class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl border-2 border-brown-300 p-8 max-w-md w-full mx-4 shadow-2xl"> 294 302 <h3 class="text-xl font-semibold mb-4 text-brown-900" x-text="editingBrewer ? 'Edit Brewer' : 'Add Brewer'"></h3> 295 303 <div class="space-y-4"> 296 304 <input type="text" x-model="brewerForm.name" placeholder="Name *" 305 + class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600" /> 306 + <input type="text" x-model="brewerForm.brewer_type" placeholder="Type (e.g., Pour-Over, Immersion, Espresso)" 297 307 class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600" /> 298 308 <textarea x-model="brewerForm.description" placeholder="Description" rows="3" 299 309 class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"></textarea>
+1
templates/partials/new_brewer_form.tmpl
··· 4 4 <h4 class="font-medium mb-3 text-gray-800">Add New Brewer</h4> 5 5 <div class="space-y-3"> 6 6 <input type="text" x-model="newBrewer.name" placeholder="Name (e.g. V60, AeroPress) *" class="w-full rounded-md border-gray-300 bg-white shadow-sm py-2 px-3"/> 7 + <input type="text" x-model="newBrewer.brewer_type" placeholder="Type (e.g. Pour-Over, Immersion)" class="w-full rounded-md border-gray-300 bg-white shadow-sm py-2 px-3"/> 7 8 <input type="text" x-model="newBrewer.description" placeholder="Description (optional)" class="w-full rounded-md border-gray-300 bg-white shadow-sm py-2 px-3"/> 8 9 <div class="flex gap-2"> 9 10 <button type="button" @click="addBrewer()" class="bg-brown-600 text-white px-4 py-2 rounded hover:bg-brown-700">Add</button>
+12 -8
templates/profile.tmpl
··· 91 91 <table class="min-w-full divide-y divide-brown-300"> 92 92 <thead class="bg-brown-200/80"> 93 93 <tr> 94 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">Name</th> 95 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">☕ Roaster</th> 96 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">📍 Origin</th> 97 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">🔥 Roast</th> 98 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">🌱 Process</th> 99 - <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">📝 Description</th> 94 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider whitespace-nowrap">Name</th> 95 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider whitespace-nowrap">☕ Roaster</th> 96 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider whitespace-nowrap">📍 Origin</th> 97 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider whitespace-nowrap">🔥 Roast</th> 98 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider whitespace-nowrap">🌱 Process</th> 99 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider whitespace-nowrap">📝 Description</th> 100 100 </tr> 101 101 </thead> 102 102 <tbody class="bg-brown-50/60 divide-y divide-brown-200"> ··· 135 135 <!-- Roasters --> 136 136 {{if .Roasters}} 137 137 <div> 138 - <h3 class="text-lg font-semibold text-brown-900 mb-3">🏭 Favorite Roasters</h3> 138 + <h3 class="text-lg font-semibold text-brown-900 mb-3">🏪 Favorite Roasters</h3> 139 139 <div class="overflow-x-auto bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-xl border border-brown-300"> 140 140 <table class="min-w-full divide-y divide-brown-300"> 141 141 <thead class="bg-brown-200/80"> ··· 219 219 <!-- Brewers --> 220 220 {{if .Brewers}} 221 221 <div> 222 - <h3 class="text-lg font-semibold text-brown-900 mb-3">🫖 Brewers</h3> 222 + <h3 class="text-lg font-semibold text-brown-900 mb-3">☕ Brewers</h3> 223 223 <div class="overflow-x-auto bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-xl border border-brown-300"> 224 224 <table class="min-w-full divide-y divide-brown-300"> 225 225 <thead class="bg-brown-200/80"> 226 226 <tr> 227 227 <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">Name</th> 228 + <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">🔧 Type</th> 228 229 <th class="px-6 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider">📝 Description</th> 229 230 </tr> 230 231 </thead> ··· 232 233 {{range .Brewers}} 233 234 <tr class="hover:bg-brown-100/60 transition-colors"> 234 235 <td class="px-6 py-4 text-sm font-bold text-brown-900">{{.Name}}</td> 236 + <td class="px-6 py-4 text-sm text-brown-900"> 237 + {{if .BrewerType}}{{.BrewerType}}{{else}}<span class="text-brown-400">-</span>{{end}} 238 + </td> 235 239 <td class="px-6 py-4 text-sm text-brown-700 italic max-w-xs"> 236 240 {{if .Description}}{{.Description}}{{else}}<span class="text-brown-400 not-italic">-</span>{{end}} 237 241 </td>
+2 -2
web/static/js/brew-form.js
··· 12 12 pours: [], 13 13 newBean: { name: '', origin: '', roasterRKey: '', roastLevel: '', process: '', description: '' }, 14 14 newGrinder: { name: '', grinderType: '', burrType: '', notes: '' }, 15 - newBrewer: { name: '', description: '' }, 15 + newBrewer: { name: '', brewer_type: '', description: '' }, 16 16 17 17 // Dropdown data 18 18 beans: [], ··· 296 296 } 297 297 // Close modal and reset form 298 298 this.showNewBrewer = false; 299 - this.newBrewer = { name: '', description: '' }; 299 + this.newBrewer = { name: '', brewer_type: '', description: '' }; 300 300 } else { 301 301 const errorText = await response.text(); 302 302 alert('Failed to add brewer: ' + errorText);
+11 -3
web/static/js/manage-page.js
··· 16 16 beanForm: {name: '', origin: '', roast_level: '', process: '', description: '', roaster_rkey: ''}, 17 17 roasterForm: {name: '', location: '', website: ''}, 18 18 grinderForm: {name: '', grinder_type: '', burr_type: '', notes: ''}, 19 - brewerForm: {name: '', description: ''}, 19 + brewerForm: {name: '', brewer_type: '', description: ''}, 20 20 21 21 init() { 22 22 this.$watch('tab', value => { ··· 200 200 } 201 201 }, 202 202 203 - editBrewer(rkey, name, description) { 203 + editBrewer(rkey, name, brewer_type, description) { 204 204 this.editingBrewer = rkey; 205 - this.brewerForm = {name, description}; 205 + this.brewerForm = {name, brewer_type, description}; 206 206 this.showBrewerForm = true; 207 + }, 208 + 209 + editBrewerFromRow(row) { 210 + const rkey = row.dataset.rkey; 211 + const name = row.dataset.name; 212 + const brewer_type = row.dataset.brewerType || ''; 213 + const description = row.dataset.description || ''; 214 + this.editBrewer(rkey, name, brewer_type, description); 207 215 }, 208 216 209 217 async saveBrewer() {