poohr vor 3 Wochen
Ursprung
Commit
4b93f3cf87

+ 1
- 0
src/app/models/danka.ts Datei anzeigen

@@ -7,6 +7,7 @@ export interface Danka {
7 7
   householderFurigana: string;
8 8
   postalCode: string;
9 9
   address: string;
10
+  note: string;
10 11
   phones: Phone[];
11 12
   updatedAt: string;
12 13
 }

+ 1
- 0
src/app/models/event.ts Datei anzeigen

@@ -12,5 +12,6 @@ export interface EventTarget {
12 12
   birthDate: string;
13 13
   age: number;
14 14
   eventType: EventType;
15
+  note: string;
15 16
   status: EventStatus;
16 17
 }

+ 1
- 0
src/app/models/memorial.ts Datei anzeigen

@@ -8,4 +8,5 @@ export interface Memorial {
8 8
   householdName: string;
9 9
   deathDate: string;
10 10
   memorialType: string;
11
+  note: string;
11 12
 }

+ 2
- 0
src/app/pages/danka-detail/danka-detail.ts Datei anzeigen

@@ -141,6 +141,7 @@ export class DankaDetail {
141 141
             birthDate: family.birthDate,
142 142
             age,
143 143
             eventType,
144
+            note: family.note,
144 145
             status:
145 146
               this.eventStatusByTargetId[id] ?? (Number(family.id) % 2 === 0 ? '案内済' : '未案内'),
146 147
           };
@@ -169,6 +170,7 @@ export class DankaDetail {
169 170
         target.birthDate,
170 171
         target.age.toString(),
171 172
         target.eventType,
173
+        target.note,
172 174
         target.status,
173 175
       ].some((value) => value.includes(keyword)),
174 176
     );

+ 10
- 0
src/app/pages/danka-edit/danka-edit.html Datei anzeigen

@@ -84,6 +84,16 @@
84 84
                   formControlName="address"
85 85
                 />
86 86
               </div>
87
+
88
+              <div class="form-row note-row">
89
+                <label for="note">備考</label>
90
+                <textarea
91
+                  id="note"
92
+                  formControlName="note"
93
+                  rows="4"
94
+                  placeholder="檀家に関する連絡事項や注意点を入力"
95
+                ></textarea>
96
+              </div>
87 97
             </div>
88 98
           </section>
89 99
 

+ 25
- 4
src/app/pages/danka-edit/danka-edit.scss Datei anzeigen

@@ -49,6 +49,7 @@
49 49
 }
50 50
 
51 51
 .edit-form input,
52
+.edit-form textarea,
52 53
 .edit-form button {
53 54
   font-family: inherit;
54 55
 }
@@ -105,10 +106,9 @@
105 106
   font-weight: 800;
106 107
 }
107 108
 
108
-.form-row input {
109
+.form-row input,
110
+.form-row textarea {
109 111
   width: 100%;
110
-  height: 54px;
111
-  padding: 0 14px;
112 112
   border: 2px solid #d8caba;
113 113
   border-radius: 8px;
114 114
   background: #fffdf9;
@@ -119,7 +119,28 @@
119 119
   outline: none;
120 120
 }
121 121
 
122
-.form-row input:focus {
122
+.form-row input {
123
+  height: 54px;
124
+  padding: 0 14px;
125
+}
126
+
127
+.form-row textarea {
128
+  min-height: 112px;
129
+  padding: 12px 14px;
130
+  line-height: 1.6;
131
+  resize: vertical;
132
+}
133
+
134
+.note-row {
135
+  align-items: start;
136
+}
137
+
138
+.note-row label {
139
+  padding-top: 12px;
140
+}
141
+
142
+.form-row input:focus,
143
+.form-row textarea:focus {
123 144
   border-color: #8a6543;
124 145
   box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.15);
125 146
 }

+ 3
- 0
src/app/pages/danka-edit/danka-edit.ts Datei anzeigen

@@ -29,6 +29,7 @@ export class DankaEdit {
29 29
     householderFurigana: new FormControl(''),
30 30
     postalCode: new FormControl('', Validators.pattern(/^\d{3}-\d{4}$/)),
31 31
     address: new FormControl(''),
32
+    note: new FormControl(''),
32 33
     phones: new FormArray([this.createPhoneForm('', '')]),
33 34
   });
34 35
 
@@ -49,6 +50,7 @@ export class DankaEdit {
49 50
           householderFurigana: this.danka.householderFurigana,
50 51
           postalCode: this.danka.postalCode,
51 52
           address: this.danka.address,
53
+          note: this.danka.note,
52 54
         });
53 55
 
54 56
         this.phones.clear();
@@ -99,6 +101,7 @@ export class DankaEdit {
99 101
       householderFurigana: formValue.householderFurigana?.trim() ?? '',
100 102
       postalCode: formValue.postalCode?.trim() ?? '',
101 103
       address: formValue.address?.trim() ?? '',
104
+      note: formValue.note?.trim() ?? '',
102 105
       updatedAt: this.formatDateForSave(new Date()),
103 106
       phones: (formValue.phones ?? [])
104 107
         .map((phone) => ({

+ 10
- 4
src/app/pages/danka-list/danka-list.html Datei anzeigen

@@ -51,8 +51,8 @@
51 51
       <div class="list-content">
52 52
         <div class="danka-table">
53 53
           <div class="danka-table-header">
54
-            <div>施主名</div>
55
-            <div>檀家名</div>
54
+            <div>檀家名・ふりがな</div>
55
+            <div>施主名・ふりがな</div>
56 56
             <div>住所</div>
57 57
             <div>電話</div>
58 58
           </div>
@@ -63,8 +63,14 @@
63 63
 
64 64
           @for (danka of filterDankaList; track danka.id) {
65 65
             <a class="danka-table-row" [routerLink]="['/danka-detail', danka.id]">
66
-              <div class="strong">{{ danka.householder }}</div>
67
-              <div>{{ danka.householdName }}</div>
66
+              <div>
67
+                <p class="danka-name">{{ danka.householdName }}</p>
68
+                <p class="danka-sub">{{ danka.householdFurigana || 'ふりがな未登録' }}</p>
69
+              </div>
70
+              <div>
71
+                <p class="danka-name">{{ danka.householder }}</p>
72
+                <p class="danka-sub">{{ danka.householderFurigana || 'ふりがな未登録' }}</p>
73
+              </div>
68 74
               <div>{{ danka.address }}</div>
69 75
               <div>{{ danka.phones[0]?.tel }}</div>
70 76
             </a>

+ 27
- 21
src/app/pages/danka-list/danka-list.scss Datei anzeigen

@@ -170,56 +170,62 @@
170 170
 
171 171
 .danka-table {
172 172
   width: 100%;
173
+  display: grid;
174
+  gap: 0;
175
+  border: 2px solid #d8c2aa;
176
+  border-radius: 8px;
177
+  overflow: hidden;
173 178
 }
174 179
 
175 180
 .danka-table-header,
176 181
 .danka-table-row {
177 182
   display: grid;
178
-  grid-template-columns: 1.5fr 1.1fr 2.8fr 1.25fr 0.65fr;
183
+  grid-template-columns: 1.2fr 1.2fr 2.2fr 1fr;
179 184
   align-items: center;
180 185
   justify-items: start;
181
-  column-gap: 16px;
186
+  gap: 12px;
182 187
   text-align: left;
183 188
 }
184 189
 
185 190
 .danka-table-header {
186
-  min-height: 36px;
187
-  padding: 0 10px;
188
-  background: #eadfce;
189
-  border: 2px solid #d8caba;
190
-  border-radius: 6px;
191
+  min-height: 46px;
192
+  padding: 0 14px;
193
+  background: #efe4d6;
191 194
   box-sizing: border-box;
192
-  color: #5a4a3c;
195
+  color: #111111;
193 196
   font-size: 14px;
194
-  font-weight: 700;
197
+  font-weight: 800;
195 198
 }
196 199
 
197 200
 .danka-table-row {
198
-  min-height: 54px;
199
-  margin-top: 4px;
200
-  padding: 0 10px;
201
-  background: #fbf7f1;
202
-  border: 2px solid #d8caba;
203
-  border-radius: 8px;
201
+  min-height: 78px;
202
+  padding: 10px 14px;
203
+  background: #fffdf9;
204
+  border-top: 1px solid #d8c2aa;
204 205
   box-sizing: border-box;
205
-  color: #2f2720;
206
+  color: #111111;
206 207
   font-size: 15px;
207 208
   text-decoration: none;
208 209
 }
209 210
 
210 211
 .danka-table-row:hover {
211
-  background: #f3eadc;
212
+  background: #fff8ee;
212 213
 }
213 214
 
214
-.danka-table-row .strong {
215
+.danka-name {
216
+  margin: 0;
215 217
   font-weight: 800;
216 218
 }
217 219
 
220
+.danka-sub {
221
+  margin: 4px 0 0;
222
+  color: #111111;
223
+  font-size: 13px;
224
+  line-height: 1.35;
225
+}
226
+
218 227
 .empty-message {
219
-  margin-top: 16px;
220 228
   padding: 24px;
221
-  border: 2px dashed #d8caba;
222
-  border-radius: 12px;
223 229
   background: #fffdf9;
224 230
   color: #7b6b5c;
225 231
   font-size: 16px;

+ 53
- 23
src/app/pages/event/event.html Datei anzeigen

@@ -10,34 +10,56 @@
10 10
           <h1>行事対象者一覧</h1>
11 11
 
12 12
           <div class="filter-row">
13
-            <div class="year-filter">
14
-              <label for="targetYear">対象年</label>
15
-              <input id="targetYear" type="number" [(ngModel)]="targetYear" />
13
+            <div class="filter-field">
14
+              <label for="targetYear">年度</label>
15
+              <select
16
+                id="targetYear"
17
+                [(ngModel)]="targetYear"
18
+                (ngModelChange)="createEventTargetList()"
19
+              >
20
+                @for (year of yearOptions; track year) {
21
+                  <option [ngValue]="year">{{ year }}年度</option>
22
+                }
23
+              </select>
16 24
             </div>
17 25
 
18
-            <div class="event-filter">
19
-              <span>行事</span>
20
-              @for (filter of eventTypeFilters; track filter.value) {
21
-                <button
22
-                  type="button"
23
-                  class="filter-button"
24
-                  [class.active]="selectedEventType === filter.value"
25
-                  (click)="changeEventType(filter.value)"
26
-                >
27
-                  {{ filter.label }}
28
-                </button>
29
-              }
26
+            <div class="filter-field">
27
+              <label for="eventType">行事</label>
28
+              <select
29
+                id="eventType"
30
+                [(ngModel)]="selectedEventType"
31
+                (ngModelChange)="changeEventType($event)"
32
+              >
33
+                @for (filter of eventTypeFilters; track filter.value) {
34
+                  <option [ngValue]="filter.value">{{ filter.label }}</option>
35
+                }
36
+              </select>
37
+            </div>
38
+
39
+            <div class="filter-field">
40
+              <label for="eventStatus">状態</label>
41
+              <select id="eventStatus" [(ngModel)]="selectedStatus">
42
+                @for (filter of statusFilters; track filter.value) {
43
+                  <option [ngValue]="filter.value">{{ filter.label }}</option>
44
+                }
45
+              </select>
46
+            </div>
47
+
48
+            <div class="search-field">
49
+              <label for="eventSearch">検索</label>
50
+              <input
51
+                id="eventSearch"
52
+                type="text"
53
+                [(ngModel)]="searchKeyword"
54
+                placeholder="氏名・ふりがな・檀家名で検索"
55
+              />
30 56
             </div>
31 57
           </div>
32 58
         </div>
33
-
34
-        <button type="button" class="reload-button" (click)="createEventTargetList()">
35
-          再表示
36
-        </button>
37 59
       </div>
38 60
 
39 61
       <div class="list-header-row">
40
-        <h2>対象 {{ eventTargets.length }} 名</h2>
62
+        <h2>対象 {{ filteredEventTargets.length }} 名</h2>
41 63
 
42 64
         <p>並び順: 行事 / 年齢 / 氏名</p>
43 65
       </div>
@@ -50,11 +72,12 @@
50 72
             <div>続柄</div>
51 73
             <div>生年月日・年齢</div>
52 74
             <div>対象行事</div>
75
+            <div>備考</div>
53 76
             <div>状態</div>
54 77
           </div>
55 78
 
56
-          @if (eventTargets.length > 0) {
57
-            @for (target of eventTargets; track target.id) {
79
+          @if (filteredEventTargets.length > 0) {
80
+            @for (target of filteredEventTargets; track target.id) {
58 81
               <div class="event-table-row">
59 82
                 <div>
60 83
                   <p class="person-name">{{ target.name }}</p>
@@ -73,6 +96,9 @@
73 96
                 <div class="event-type">
74 97
                   {{ target.eventType }}
75 98
                 </div>
99
+                <div>
100
+                  {{ target.note || '' }}
101
+                </div>
76 102
                 <div>
77 103
                   <select
78 104
                     class="status-select"
@@ -90,7 +116,11 @@
90 116
             }
91 117
           } @else {
92 118
             <div class="empty-message">
93
-              対象となる行事対象者はありません。
119
+              @if (eventTargets.length > 0) {
120
+                検索条件に一致する行事対象者はありません。
121
+              } @else {
122
+                対象となる行事対象者はありません。
123
+              }
94 124
             </div>
95 125
           }
96 126
         </div>

+ 31
- 68
src/app/pages/event/event.scss Datei anzeigen

@@ -53,92 +53,57 @@
53 53
 
54 54
 .filter-row {
55 55
   display: flex;
56
-  align-items: flex-start;
57
-  gap: 16px 28px;
56
+  align-items: flex-end;
57
+  gap: 14px 18px;
58 58
   flex-wrap: wrap;
59
+  width: 100%;
59 60
 }
60 61
 
61
-.year-filter {
62
-  display: flex;
63
-  align-items: center;
64
-  gap: 12px;
62
+.filter-field,
63
+.search-field {
64
+  display: grid;
65
+  gap: 6px;
65 66
 }
66 67
 
67
-.year-filter label,
68
-.event-filter span {
68
+.filter-field label,
69
+.search-field label {
69 70
   color: #4b3c31;
70
-  font-size: 16px;
71
+  font-size: 14px;
71 72
   font-weight: 800;
72 73
 }
73 74
 
74
-.year-filter input {
75
-  width: 96px;
76
-  height: 48px;
75
+.filter-field select,
76
+.search-field input {
77
+  height: 38px;
77 78
   padding: 0 14px;
78 79
   border: 2px solid #d8caba;
79 80
   border-radius: 8px;
80 81
   background: #fffdf9;
81 82
   color: #2f2720;
82
-  font-size: 17px;
83
+  font-size: 15px;
83 84
   font-weight: 700;
84 85
   box-sizing: border-box;
86
+  outline: none;
85 87
 }
86 88
 
87
-.event-filter {
88
-  display: flex;
89
-  align-items: flex-start;
90
-  flex-wrap: wrap;
91
-  gap: 8px;
92
-}
93
-
94
-.event-filter span {
95
-  min-height: 36px;
96
-  display: flex;
97
-  align-items: center;
98
-}
99
-
100
-.filter-button {
101
-  min-width: 86px;
102
-  height: 36px;
103
-  padding: 0 12px;
104
-  border: 2px solid #8a6543;
105
-  border-radius: 6px;
106
-  background: #ffffff;
107
-  color: #8a6543;
108
-  font-size: 14px;
109
-  font-weight: 800;
110
-  white-space: nowrap;
89
+.filter-field select {
90
+  width: 148px;
111 91
   cursor: pointer;
112
-  box-sizing: border-box;
113 92
 }
114 93
 
115
-.filter-button:hover {
116
-  background: #f6efe6;
94
+.search-field {
95
+  flex: 1 1 260px;
96
+  min-width: 260px;
117 97
 }
118 98
 
119
-.filter-button.active {
120
-  background: #f6efe6;
121
-  border-color: #8a6543;
122
-  color: #8a6543;
99
+.search-field input {
100
+  width: 100%;
123 101
 }
124 102
 
125
-.reload-button {
126
-  flex: 0 0 auto;
127
-  width: 140px;
128
-  height: 46px;
129
-  margin-top: 36px;
130
-  border: 2px solid #8a6543;
131
-  border-radius: 6px;
132
-  background: #ffffff;
133
-  color: #8a6543;
134
-  font-size: 18px;
135
-  font-weight: 800;
136
-  cursor: pointer;
137
-  box-sizing: border-box;
138
-}
139
-
140
-.reload-button:hover {
141
-  background: #f6efe6;
103
+.filter-field select:focus,
104
+.search-field input:focus {
105
+  border-color: #8a6543;
106
+  box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.12);
142 107
 }
143 108
 
144 109
 .list-header-row {
@@ -176,7 +141,7 @@
176 141
 .event-table-header,
177 142
 .event-table-row {
178 143
   display: grid;
179
-  grid-template-columns: 1.35fr 1.05fr 0.75fr 1.05fr 0.95fr 96px;
144
+  grid-template-columns: 1.25fr 0.95fr 0.68fr 0.95fr 0.82fr 1fr 96px;
180 145
   align-items: center;
181 146
   gap: 12px;
182 147
 }
@@ -273,10 +238,6 @@
273 238
     flex-direction: column;
274 239
   }
275 240
 
276
-  .reload-button {
277
-    margin-top: 0;
278
-  }
279
-
280 241
   .event-table {
281 242
     overflow-x: auto;
282 243
   }
@@ -312,8 +273,10 @@
312 273
     gap: 14px;
313 274
   }
314 275
 
315
-  .event-filter {
316
-    flex-wrap: wrap;
276
+  .filter-field,
277
+  .filter-field select,
278
+  .search-field {
279
+    width: 100%;
317 280
   }
318 281
 
319 282
   .list-header-row {

+ 36
- 0
src/app/pages/event/event.ts Datei anzeigen

@@ -16,7 +16,15 @@ export class EventPage {
16 16
   eventTargets: EventTarget[] = [];
17 17
   targetYear: number = new Date().getFullYear();
18 18
   selectedEventType: EventType | 'all' = 'all';
19
+  selectedStatus: EventStatus | 'all' = 'all';
20
+  searchKeyword = '';
19 21
   eventStatuses: EventStatus[] = ['未案内', '案内済'];
22
+  yearOptions: number[] = [
23
+    this.targetYear - 1,
24
+    this.targetYear,
25
+    this.targetYear + 1,
26
+    this.targetYear + 2,
27
+  ];
20 28
   eventTypeFilters: { label: string; value: EventType | 'all' }[] = [
21 29
     { label: 'すべて', value: 'all' },
22 30
     { label: '稚児行列', value: '稚児行列' },
@@ -24,6 +32,11 @@ export class EventPage {
24 32
     { label: '成人式', value: '成人式' },
25 33
     { label: '米寿', value: '米寿' },
26 34
   ];
35
+  statusFilters: { label: string; value: EventStatus | 'all' }[] = [
36
+    { label: 'すべて', value: 'all' },
37
+    { label: '未案内', value: '未案内' },
38
+    { label: '案内済', value: '案内済' },
39
+  ];
27 40
   private statusByTargetId: Record<string, EventStatus> = {};
28 41
 
29 42
   constructor(
@@ -65,6 +78,7 @@ export class EventPage {
65 78
           birthDate: family.birthDate,
66 79
           age,
67 80
           eventType,
81
+          note: family.note,
68 82
           status: this.statusByTargetId[id] ?? (Number(family.id) % 2 === 0 ? '案内済' : '未案内'),
69 83
         });
70 84
       });
@@ -83,6 +97,28 @@ export class EventPage {
83 97
     this.createEventTargetList();
84 98
   }
85 99
 
100
+  get filteredEventTargets(): EventTarget[] {
101
+    const keyword = this.searchKeyword.trim();
102
+
103
+    return this.eventTargets.filter((target) => {
104
+      const matchesStatus = this.selectedStatus === 'all' || target.status === this.selectedStatus;
105
+      const matchesKeyword =
106
+        !keyword ||
107
+        [
108
+          target.name,
109
+          target.furigana,
110
+          target.householdName,
111
+          target.relationship,
112
+          target.birthDate,
113
+          target.eventType,
114
+          target.note,
115
+          target.status,
116
+        ].some((value) => value.includes(keyword));
117
+
118
+      return matchesStatus && matchesKeyword;
119
+    });
120
+  }
121
+
86 122
   changeStatus(target: EventTarget, status: EventStatus): void {
87 123
     target.status = status;
88 124
     this.statusByTargetId[target.id] = status;

+ 41
- 22
src/app/pages/memorial-list/memorial-list.html Datei anzeigen

@@ -11,36 +11,47 @@
11 11
           <h1>年次法要(回忌)一覧</h1>
12 12
 
13 13
           <div class="filter-row">
14
-            <div class="year-filter">
14
+            <div class="filter-field">
15 15
               <label for="targetYear">対象年</label>
16
-              <input id="targetYear" type="number" [(ngModel)]="targetYear"/>
16
+              <select
17
+                id="targetYear"
18
+                [(ngModel)]="targetYear"
19
+                (ngModelChange)="createMemorialList()"
20
+              >
21
+                @for (year of yearOptions; track year) {
22
+                  <option [ngValue]="year">{{ year }}年度</option>
23
+                }
24
+              </select>
17 25
             </div>
18 26
 
19
-            <div class="memorial-filter">
20
-              <span>回忌</span>
21
-              <div class="memorial-filter-buttons">
27
+            <div class="filter-field">
28
+              <label for="memorialType">回忌</label>
29
+              <select
30
+                id="memorialType"
31
+                [(ngModel)]="selectedMemorialType"
32
+                (ngModelChange)="changeMemorialType($event)"
33
+              >
22 34
                 @for (filter of memorialTypeFilters; track filter.value) {
23
-                  <button
24
-                    type="button"
25
-                    class="filter-button"
26
-                    [class.active]="selectedMemorialType === filter.value"
27
-                    (click)="changeMemorialType(filter.value)"
28
-                  >
29
-                    {{ filter.label }}
30
-                  </button>
35
+                  <option [ngValue]="filter.value">{{ filter.label }}</option>
31 36
                 }
32
-              </div>
37
+              </select>
38
+            </div>
39
+
40
+            <div class="search-field">
41
+              <label for="memorialSearch">検索</label>
42
+              <input
43
+                id="memorialSearch"
44
+                type="text"
45
+                [(ngModel)]="searchKeyword"
46
+                placeholder="戒名・俗名・檀家名で検索"
47
+              />
33 48
             </div>
34 49
           </div>
35 50
         </div>
36
-
37
-        <button type="button" class="reload-button" (click)="createMemorialList()">
38
-          再表示
39
-        </button>
40 51
       </div>
41 52
 
42 53
       <div class="list-header-row">
43
-        <h2>対象 {{ memorialList.length }} 名</h2>
54
+        <h2>対象 {{ filteredMemorialList.length }} 名</h2>
44 55
 
45 56
         <p>並び順: 没年月日 / 氏名</p>
46 57
       </div>
@@ -54,11 +65,12 @@
54 65
             <div>関係</div>
55 66
             <div>檀家(世帯)</div>
56 67
             <div>回忌</div>
68
+            <div>備考</div>
57 69
             <div>詳細</div>
58 70
           </div>
59 71
 
60
-          @if (memorialList.length > 0) {
61
-            @for (memorial of memorialList; track memorial.id) {
72
+          @if (filteredMemorialList.length > 0) {
73
+            @for (memorial of filteredMemorialList; track memorial.id) {
62 74
               <div class="memorial-table-row">
63 75
                 <div class="person-name">
64 76
                   {{ memorial.kaimyo }}
@@ -79,6 +91,9 @@
79 91
                 <div class="memorial-type">
80 92
                   {{ memorial.memorialType }}
81 93
                 </div>
94
+                <div>
95
+                  {{ memorial.note || '' }}
96
+                </div>
82 97
                 <div>
83 98
                   <a class="detail-link" [routerLink]="['/danka-detail', memorial.dankaId]" [queryParams]="{tab: 'kakocho'}">
84 99
                     開く
@@ -88,7 +103,11 @@
88 103
             }
89 104
           } @else {
90 105
             <div class="empty-message">
91
-              対象となる法要はありません。
106
+              @if (memorialList.length > 0) {
107
+                検索条件に一致する法要はありません。
108
+              } @else {
109
+                対象となる法要はありません。
110
+              }
92 111
             </div>
93 112
           }
94 113
         </div>

+ 33
- 78
src/app/pages/memorial-list/memorial-list.scss Datei anzeigen

@@ -53,104 +53,61 @@
53 53
 
54 54
 .filter-row {
55 55
   display: flex;
56
-  align-items: flex-start;
57
-  gap: 16px 28px;
56
+  align-items: flex-end;
57
+  gap: 14px 18px;
58 58
   flex-wrap: wrap;
59
+  width: 100%;
59 60
 }
60 61
 
61
-.year-filter {
62
-  display: flex;
63
-  align-items: center;
64
-  gap: 12px;
62
+.filter-field,
63
+.search-field {
64
+  display: grid;
65
+  gap: 6px;
65 66
 }
66 67
 
67
-.year-filter label,
68
-.memorial-filter span {
68
+.filter-field label,
69
+.search-field label {
69 70
   color: #4b3c31;
70
-  font-size: 16px;
71
+  font-size: 14px;
71 72
   font-weight: 800;
72 73
 }
73 74
 
74
-.year-filter input {
75
-  width: 96px;
76
-  height: 48px;
75
+.filter-field select,
76
+.search-field input {
77
+  height: 38px;
77 78
   padding: 0 14px;
78 79
   border: 2px solid #d8caba;
79 80
   border-radius: 8px;
80 81
   background: #fffdf9;
81 82
   color: #2f2720;
82
-  font-size: 17px;
83
+  font-size: 15px;
83 84
   font-weight: 700;
84 85
   box-sizing: border-box;
86
+  outline: none;
85 87
 }
86 88
 
87
-.memorial-filter {
88
-  display: flex;
89
-  align-items: flex-start;
90
-  flex-wrap: nowrap;
91
-  gap: 8px;
92
-  max-width: 100%;
93
-}
94
-
95
-.memorial-filter span {
96
-  min-height: 36px;
97
-  display: flex;
98
-  align-items: center;
99
-}
100
-
101
-.memorial-filter-buttons {
102
-  display: flex;
103
-  align-items: center;
104
-  flex-wrap: nowrap;
105
-  gap: 6px;
106
-  max-width: 100%;
107
-  overflow-x: auto;
108
-  padding-bottom: 2px;
109
-}
110
-
111
-.filter-button {
112
-  flex: 0 0 72px;
113
-  min-width: 72px;
114
-  height: 36px;
115
-  padding: 0 6px;
116
-  border: 2px solid #8a6543;
117
-  border-radius: 6px;
118
-  background: #ffffff;
119
-  color: #8a6543;
120
-  font-size: 12px;
121
-  font-weight: 800;
122
-  white-space: nowrap;
89
+.filter-field select {
90
+  width: 148px;
123 91
   cursor: pointer;
124
-  box-sizing: border-box;
125 92
 }
126 93
 
127
-.filter-button:hover {
128
-  background: #f6efe6;
94
+.search-field {
95
+  flex: 1 1 260px;
96
+  min-width: 260px;
129 97
 }
130 98
 
131
-.filter-button.active {
132
-  background: #f6efe6;
133
-  border-color: #8a6543;
134
-  color: #8a6543;
99
+.search-field input {
100
+  width: 100%;
135 101
 }
136 102
 
137
-.reload-button {
138
-  flex: 0 0 auto;
139
-  width: 140px;
140
-  height: 46px;
141
-  margin-top: 36px;
142
-  border: 2px solid #8a6543;
143
-  border-radius: 6px;
144
-  background: #ffffff;
145
-  color: #8a6543;
146
-  font-size: 18px;
147
-  font-weight: 800;
148
-  cursor: pointer;
149
-  box-sizing: border-box;
103
+.filter-field select:focus {
104
+  border-color: #8a6543;
105
+  box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.12);
150 106
 }
151 107
 
152
-.reload-button:hover {
153
-  background: #f6efe6;
108
+.search-field input:focus {
109
+  border-color: #8a6543;
110
+  box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.12);
154 111
 }
155 112
 
156 113
 .list-header-row {
@@ -188,7 +145,7 @@
188 145
 .memorial-table-header,
189 146
 .memorial-table-row {
190 147
   display: grid;
191
-  grid-template-columns: 1.4fr 1.2fr 0.9fr 0.8fr 1.2fr 0.8fr 0.7fr;
148
+  grid-template-columns: 1.2fr 0.95fr 0.8fr 0.6fr 0.95fr 0.65fr 0.95fr 64px;
192 149
   align-items: center;
193 150
   gap: 12px;
194 151
 }
@@ -270,10 +227,6 @@
270 227
     flex-direction: column;
271 228
   }
272 229
 
273
-  .reload-button {
274
-    margin-top: 0;
275
-  }
276
-
277 230
   .memorial-table {
278 231
     overflow-x: auto;
279 232
   }
@@ -309,8 +262,10 @@
309 262
     gap: 14px;
310 263
   }
311 264
 
312
-  .memorial-filter {
313
-    flex-wrap: wrap;
265
+  .filter-field,
266
+  .filter-field select,
267
+  .search-field {
268
+    width: 100%;
314 269
   }
315 270
 
316 271
   .list-header-row {

+ 27
- 0
src/app/pages/memorial-list/memorial-list.ts Datei anzeigen

@@ -17,6 +17,13 @@ export class MemorialList {
17 17
   memorialList: Memorial[] = [];
18 18
   targetYear: number = new Date().getFullYear();
19 19
   selectedMemorialType = 'all';
20
+  searchKeyword = '';
21
+  yearOptions: number[] = [
22
+    this.targetYear - 1,
23
+    this.targetYear,
24
+    this.targetYear + 1,
25
+    this.targetYear + 2,
26
+  ];
20 27
   memorialTypeFilters = [
21 28
     { label: 'すべて', value: 'all' },
22 29
     { label: '一周忌', value: '一周忌' },
@@ -65,6 +72,7 @@ export class MemorialList {
65 72
         householdName: danka?.householdName ?? '不明',
66 73
         deathDate: kakocho.deathDate,
67 74
         memorialType: memorialType,
75
+        note: kakocho.note,
68 76
       };
69 77
       this.memorialList.push(memorialTarget);
70 78
     });
@@ -83,6 +91,25 @@ export class MemorialList {
83 91
     this.createMemorialList();
84 92
   }
85 93
 
94
+  get filteredMemorialList(): Memorial[] {
95
+    const keyword = this.searchKeyword.trim();
96
+    if (!keyword) {
97
+      return this.memorialList;
98
+    }
99
+
100
+    return this.memorialList.filter((memorial) =>
101
+      [
102
+        memorial.kaimyo,
103
+        memorial.name,
104
+        memorial.deathDate,
105
+        memorial.relationship,
106
+        memorial.householdName,
107
+        memorial.memorialType,
108
+        memorial.note,
109
+      ].some((value) => value.includes(keyword)),
110
+    );
111
+  }
112
+
86 113
   formatDeathDate(deathDate: string): string {
87 114
     const [, month, day] = deathDate.split('-').map(Number);
88 115
     if (!month || !day) {

+ 2
- 0
src/app/services/dankaService.ts Datei anzeigen

@@ -14,6 +14,7 @@ export class DankaService {
14 14
       householderFurigana: 'すずき たろう',
15 15
       postalCode: '123-4567',
16 16
       address: '市内 1-2-3',
17
+      note: '寺報送付あり。年忌法要の案内は施主へ連絡。',
17 18
       updatedAt: '2026-05-28',
18 19
       phones: [
19 20
         {
@@ -34,6 +35,7 @@ export class DankaService {
34 35
       householderFurigana: 'ふるた たろう',
35 36
       postalCode: '234-4567',
36 37
       address: '市内 1-2-3',
38
+      note: '電話連絡を優先。',
37 39
       updatedAt: '2026-05-28',
38 40
       phones: [
39 41
         {

Laden…
Abbrechen
Speichern