poohr vor 3 Wochen
Ursprung
Commit
4b93f3cf87

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

7
   householderFurigana: string;
7
   householderFurigana: string;
8
   postalCode: string;
8
   postalCode: string;
9
   address: string;
9
   address: string;
10
+  note: string;
10
   phones: Phone[];
11
   phones: Phone[];
11
   updatedAt: string;
12
   updatedAt: string;
12
 }
13
 }

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

12
   birthDate: string;
12
   birthDate: string;
13
   age: number;
13
   age: number;
14
   eventType: EventType;
14
   eventType: EventType;
15
+  note: string;
15
   status: EventStatus;
16
   status: EventStatus;
16
 }
17
 }

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

8
   householdName: string;
8
   householdName: string;
9
   deathDate: string;
9
   deathDate: string;
10
   memorialType: string;
10
   memorialType: string;
11
+  note: string;
11
 }
12
 }

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

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

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

84
                   formControlName="address"
84
                   formControlName="address"
85
                 />
85
                 />
86
               </div>
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
             </div>
97
             </div>
88
           </section>
98
           </section>
89
 
99
 

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

49
 }
49
 }
50
 
50
 
51
 .edit-form input,
51
 .edit-form input,
52
+.edit-form textarea,
52
 .edit-form button {
53
 .edit-form button {
53
   font-family: inherit;
54
   font-family: inherit;
54
 }
55
 }
105
   font-weight: 800;
106
   font-weight: 800;
106
 }
107
 }
107
 
108
 
108
-.form-row input {
109
+.form-row input,
110
+.form-row textarea {
109
   width: 100%;
111
   width: 100%;
110
-  height: 54px;
111
-  padding: 0 14px;
112
   border: 2px solid #d8caba;
112
   border: 2px solid #d8caba;
113
   border-radius: 8px;
113
   border-radius: 8px;
114
   background: #fffdf9;
114
   background: #fffdf9;
119
   outline: none;
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
   border-color: #8a6543;
144
   border-color: #8a6543;
124
   box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.15);
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
     householderFurigana: new FormControl(''),
29
     householderFurigana: new FormControl(''),
30
     postalCode: new FormControl('', Validators.pattern(/^\d{3}-\d{4}$/)),
30
     postalCode: new FormControl('', Validators.pattern(/^\d{3}-\d{4}$/)),
31
     address: new FormControl(''),
31
     address: new FormControl(''),
32
+    note: new FormControl(''),
32
     phones: new FormArray([this.createPhoneForm('', '')]),
33
     phones: new FormArray([this.createPhoneForm('', '')]),
33
   });
34
   });
34
 
35
 
49
           householderFurigana: this.danka.householderFurigana,
50
           householderFurigana: this.danka.householderFurigana,
50
           postalCode: this.danka.postalCode,
51
           postalCode: this.danka.postalCode,
51
           address: this.danka.address,
52
           address: this.danka.address,
53
+          note: this.danka.note,
52
         });
54
         });
53
 
55
 
54
         this.phones.clear();
56
         this.phones.clear();
99
       householderFurigana: formValue.householderFurigana?.trim() ?? '',
101
       householderFurigana: formValue.householderFurigana?.trim() ?? '',
100
       postalCode: formValue.postalCode?.trim() ?? '',
102
       postalCode: formValue.postalCode?.trim() ?? '',
101
       address: formValue.address?.trim() ?? '',
103
       address: formValue.address?.trim() ?? '',
104
+      note: formValue.note?.trim() ?? '',
102
       updatedAt: this.formatDateForSave(new Date()),
105
       updatedAt: this.formatDateForSave(new Date()),
103
       phones: (formValue.phones ?? [])
106
       phones: (formValue.phones ?? [])
104
         .map((phone) => ({
107
         .map((phone) => ({

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

51
       <div class="list-content">
51
       <div class="list-content">
52
         <div class="danka-table">
52
         <div class="danka-table">
53
           <div class="danka-table-header">
53
           <div class="danka-table-header">
54
-            <div>施主名</div>
55
-            <div>檀家名</div>
54
+            <div>檀家名・ふりがな</div>
55
+            <div>施主名・ふりがな</div>
56
             <div>住所</div>
56
             <div>住所</div>
57
             <div>電話</div>
57
             <div>電話</div>
58
           </div>
58
           </div>
63
 
63
 
64
           @for (danka of filterDankaList; track danka.id) {
64
           @for (danka of filterDankaList; track danka.id) {
65
             <a class="danka-table-row" [routerLink]="['/danka-detail', danka.id]">
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
               <div>{{ danka.address }}</div>
74
               <div>{{ danka.address }}</div>
69
               <div>{{ danka.phones[0]?.tel }}</div>
75
               <div>{{ danka.phones[0]?.tel }}</div>
70
             </a>
76
             </a>

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

170
 
170
 
171
 .danka-table {
171
 .danka-table {
172
   width: 100%;
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
 .danka-table-header,
180
 .danka-table-header,
176
 .danka-table-row {
181
 .danka-table-row {
177
   display: grid;
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
   align-items: center;
184
   align-items: center;
180
   justify-items: start;
185
   justify-items: start;
181
-  column-gap: 16px;
186
+  gap: 12px;
182
   text-align: left;
187
   text-align: left;
183
 }
188
 }
184
 
189
 
185
 .danka-table-header {
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
   box-sizing: border-box;
194
   box-sizing: border-box;
192
-  color: #5a4a3c;
195
+  color: #111111;
193
   font-size: 14px;
196
   font-size: 14px;
194
-  font-weight: 700;
197
+  font-weight: 800;
195
 }
198
 }
196
 
199
 
197
 .danka-table-row {
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
   box-sizing: border-box;
205
   box-sizing: border-box;
205
-  color: #2f2720;
206
+  color: #111111;
206
   font-size: 15px;
207
   font-size: 15px;
207
   text-decoration: none;
208
   text-decoration: none;
208
 }
209
 }
209
 
210
 
210
 .danka-table-row:hover {
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
   font-weight: 800;
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
 .empty-message {
227
 .empty-message {
219
-  margin-top: 16px;
220
   padding: 24px;
228
   padding: 24px;
221
-  border: 2px dashed #d8caba;
222
-  border-radius: 12px;
223
   background: #fffdf9;
229
   background: #fffdf9;
224
   color: #7b6b5c;
230
   color: #7b6b5c;
225
   font-size: 16px;
231
   font-size: 16px;

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

10
           <h1>行事対象者一覧</h1>
10
           <h1>行事対象者一覧</h1>
11
 
11
 
12
           <div class="filter-row">
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
             </div>
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
             </div>
56
             </div>
31
           </div>
57
           </div>
32
         </div>
58
         </div>
33
-
34
-        <button type="button" class="reload-button" (click)="createEventTargetList()">
35
-          再表示
36
-        </button>
37
       </div>
59
       </div>
38
 
60
 
39
       <div class="list-header-row">
61
       <div class="list-header-row">
40
-        <h2>対象 {{ eventTargets.length }} 名</h2>
62
+        <h2>対象 {{ filteredEventTargets.length }} 名</h2>
41
 
63
 
42
         <p>並び順: 行事 / 年齢 / 氏名</p>
64
         <p>並び順: 行事 / 年齢 / 氏名</p>
43
       </div>
65
       </div>
50
             <div>続柄</div>
72
             <div>続柄</div>
51
             <div>生年月日・年齢</div>
73
             <div>生年月日・年齢</div>
52
             <div>対象行事</div>
74
             <div>対象行事</div>
75
+            <div>備考</div>
53
             <div>状態</div>
76
             <div>状態</div>
54
           </div>
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
               <div class="event-table-row">
81
               <div class="event-table-row">
59
                 <div>
82
                 <div>
60
                   <p class="person-name">{{ target.name }}</p>
83
                   <p class="person-name">{{ target.name }}</p>
73
                 <div class="event-type">
96
                 <div class="event-type">
74
                   {{ target.eventType }}
97
                   {{ target.eventType }}
75
                 </div>
98
                 </div>
99
+                <div>
100
+                  {{ target.note || '' }}
101
+                </div>
76
                 <div>
102
                 <div>
77
                   <select
103
                   <select
78
                     class="status-select"
104
                     class="status-select"
90
             }
116
             }
91
           } @else {
117
           } @else {
92
             <div class="empty-message">
118
             <div class="empty-message">
93
-              対象となる行事対象者はありません。
119
+              @if (eventTargets.length > 0) {
120
+                検索条件に一致する行事対象者はありません。
121
+              } @else {
122
+                対象となる行事対象者はありません。
123
+              }
94
             </div>
124
             </div>
95
           }
125
           }
96
         </div>
126
         </div>

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

53
 
53
 
54
 .filter-row {
54
 .filter-row {
55
   display: flex;
55
   display: flex;
56
-  align-items: flex-start;
57
-  gap: 16px 28px;
56
+  align-items: flex-end;
57
+  gap: 14px 18px;
58
   flex-wrap: wrap;
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
   color: #4b3c31;
70
   color: #4b3c31;
70
-  font-size: 16px;
71
+  font-size: 14px;
71
   font-weight: 800;
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
   padding: 0 14px;
78
   padding: 0 14px;
78
   border: 2px solid #d8caba;
79
   border: 2px solid #d8caba;
79
   border-radius: 8px;
80
   border-radius: 8px;
80
   background: #fffdf9;
81
   background: #fffdf9;
81
   color: #2f2720;
82
   color: #2f2720;
82
-  font-size: 17px;
83
+  font-size: 15px;
83
   font-weight: 700;
84
   font-weight: 700;
84
   box-sizing: border-box;
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
   cursor: pointer;
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
 .list-header-row {
109
 .list-header-row {
176
 .event-table-header,
141
 .event-table-header,
177
 .event-table-row {
142
 .event-table-row {
178
   display: grid;
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
   align-items: center;
145
   align-items: center;
181
   gap: 12px;
146
   gap: 12px;
182
 }
147
 }
273
     flex-direction: column;
238
     flex-direction: column;
274
   }
239
   }
275
 
240
 
276
-  .reload-button {
277
-    margin-top: 0;
278
-  }
279
-
280
   .event-table {
241
   .event-table {
281
     overflow-x: auto;
242
     overflow-x: auto;
282
   }
243
   }
312
     gap: 14px;
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
   .list-header-row {
282
   .list-header-row {

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

16
   eventTargets: EventTarget[] = [];
16
   eventTargets: EventTarget[] = [];
17
   targetYear: number = new Date().getFullYear();
17
   targetYear: number = new Date().getFullYear();
18
   selectedEventType: EventType | 'all' = 'all';
18
   selectedEventType: EventType | 'all' = 'all';
19
+  selectedStatus: EventStatus | 'all' = 'all';
20
+  searchKeyword = '';
19
   eventStatuses: EventStatus[] = ['未案内', '案内済'];
21
   eventStatuses: EventStatus[] = ['未案内', '案内済'];
22
+  yearOptions: number[] = [
23
+    this.targetYear - 1,
24
+    this.targetYear,
25
+    this.targetYear + 1,
26
+    this.targetYear + 2,
27
+  ];
20
   eventTypeFilters: { label: string; value: EventType | 'all' }[] = [
28
   eventTypeFilters: { label: string; value: EventType | 'all' }[] = [
21
     { label: 'すべて', value: 'all' },
29
     { label: 'すべて', value: 'all' },
22
     { label: '稚児行列', value: '稚児行列' },
30
     { label: '稚児行列', value: '稚児行列' },
24
     { label: '成人式', value: '成人式' },
32
     { label: '成人式', value: '成人式' },
25
     { label: '米寿', value: '米寿' },
33
     { label: '米寿', value: '米寿' },
26
   ];
34
   ];
35
+  statusFilters: { label: string; value: EventStatus | 'all' }[] = [
36
+    { label: 'すべて', value: 'all' },
37
+    { label: '未案内', value: '未案内' },
38
+    { label: '案内済', value: '案内済' },
39
+  ];
27
   private statusByTargetId: Record<string, EventStatus> = {};
40
   private statusByTargetId: Record<string, EventStatus> = {};
28
 
41
 
29
   constructor(
42
   constructor(
65
           birthDate: family.birthDate,
78
           birthDate: family.birthDate,
66
           age,
79
           age,
67
           eventType,
80
           eventType,
81
+          note: family.note,
68
           status: this.statusByTargetId[id] ?? (Number(family.id) % 2 === 0 ? '案内済' : '未案内'),
82
           status: this.statusByTargetId[id] ?? (Number(family.id) % 2 === 0 ? '案内済' : '未案内'),
69
         });
83
         });
70
       });
84
       });
83
     this.createEventTargetList();
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
   changeStatus(target: EventTarget, status: EventStatus): void {
122
   changeStatus(target: EventTarget, status: EventStatus): void {
87
     target.status = status;
123
     target.status = status;
88
     this.statusByTargetId[target.id] = status;
124
     this.statusByTargetId[target.id] = status;

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

11
           <h1>年次法要(回忌)一覧</h1>
11
           <h1>年次法要(回忌)一覧</h1>
12
 
12
 
13
           <div class="filter-row">
13
           <div class="filter-row">
14
-            <div class="year-filter">
14
+            <div class="filter-field">
15
               <label for="targetYear">対象年</label>
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
             </div>
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
                 @for (filter of memorialTypeFilters; track filter.value) {
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
             </div>
48
             </div>
34
           </div>
49
           </div>
35
         </div>
50
         </div>
36
-
37
-        <button type="button" class="reload-button" (click)="createMemorialList()">
38
-          再表示
39
-        </button>
40
       </div>
51
       </div>
41
 
52
 
42
       <div class="list-header-row">
53
       <div class="list-header-row">
43
-        <h2>対象 {{ memorialList.length }} 名</h2>
54
+        <h2>対象 {{ filteredMemorialList.length }} 名</h2>
44
 
55
 
45
         <p>並び順: 没年月日 / 氏名</p>
56
         <p>並び順: 没年月日 / 氏名</p>
46
       </div>
57
       </div>
54
             <div>関係</div>
65
             <div>関係</div>
55
             <div>檀家(世帯)</div>
66
             <div>檀家(世帯)</div>
56
             <div>回忌</div>
67
             <div>回忌</div>
68
+            <div>備考</div>
57
             <div>詳細</div>
69
             <div>詳細</div>
58
           </div>
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
               <div class="memorial-table-row">
74
               <div class="memorial-table-row">
63
                 <div class="person-name">
75
                 <div class="person-name">
64
                   {{ memorial.kaimyo }}
76
                   {{ memorial.kaimyo }}
79
                 <div class="memorial-type">
91
                 <div class="memorial-type">
80
                   {{ memorial.memorialType }}
92
                   {{ memorial.memorialType }}
81
                 </div>
93
                 </div>
94
+                <div>
95
+                  {{ memorial.note || '' }}
96
+                </div>
82
                 <div>
97
                 <div>
83
                   <a class="detail-link" [routerLink]="['/danka-detail', memorial.dankaId]" [queryParams]="{tab: 'kakocho'}">
98
                   <a class="detail-link" [routerLink]="['/danka-detail', memorial.dankaId]" [queryParams]="{tab: 'kakocho'}">
84
                     開く
99
                     開く
88
             }
103
             }
89
           } @else {
104
           } @else {
90
             <div class="empty-message">
105
             <div class="empty-message">
91
-              対象となる法要はありません。
106
+              @if (memorialList.length > 0) {
107
+                検索条件に一致する法要はありません。
108
+              } @else {
109
+                対象となる法要はありません。
110
+              }
92
             </div>
111
             </div>
93
           }
112
           }
94
         </div>
113
         </div>

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

53
 
53
 
54
 .filter-row {
54
 .filter-row {
55
   display: flex;
55
   display: flex;
56
-  align-items: flex-start;
57
-  gap: 16px 28px;
56
+  align-items: flex-end;
57
+  gap: 14px 18px;
58
   flex-wrap: wrap;
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
   color: #4b3c31;
70
   color: #4b3c31;
70
-  font-size: 16px;
71
+  font-size: 14px;
71
   font-weight: 800;
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
   padding: 0 14px;
78
   padding: 0 14px;
78
   border: 2px solid #d8caba;
79
   border: 2px solid #d8caba;
79
   border-radius: 8px;
80
   border-radius: 8px;
80
   background: #fffdf9;
81
   background: #fffdf9;
81
   color: #2f2720;
82
   color: #2f2720;
82
-  font-size: 17px;
83
+  font-size: 15px;
83
   font-weight: 700;
84
   font-weight: 700;
84
   box-sizing: border-box;
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
   cursor: pointer;
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
 .list-header-row {
113
 .list-header-row {
188
 .memorial-table-header,
145
 .memorial-table-header,
189
 .memorial-table-row {
146
 .memorial-table-row {
190
   display: grid;
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
   align-items: center;
149
   align-items: center;
193
   gap: 12px;
150
   gap: 12px;
194
 }
151
 }
270
     flex-direction: column;
227
     flex-direction: column;
271
   }
228
   }
272
 
229
 
273
-  .reload-button {
274
-    margin-top: 0;
275
-  }
276
-
277
   .memorial-table {
230
   .memorial-table {
278
     overflow-x: auto;
231
     overflow-x: auto;
279
   }
232
   }
309
     gap: 14px;
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
   .list-header-row {
271
   .list-header-row {

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

17
   memorialList: Memorial[] = [];
17
   memorialList: Memorial[] = [];
18
   targetYear: number = new Date().getFullYear();
18
   targetYear: number = new Date().getFullYear();
19
   selectedMemorialType = 'all';
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
   memorialTypeFilters = [
27
   memorialTypeFilters = [
21
     { label: 'すべて', value: 'all' },
28
     { label: 'すべて', value: 'all' },
22
     { label: '一周忌', value: '一周忌' },
29
     { label: '一周忌', value: '一周忌' },
65
         householdName: danka?.householdName ?? '不明',
72
         householdName: danka?.householdName ?? '不明',
66
         deathDate: kakocho.deathDate,
73
         deathDate: kakocho.deathDate,
67
         memorialType: memorialType,
74
         memorialType: memorialType,
75
+        note: kakocho.note,
68
       };
76
       };
69
       this.memorialList.push(memorialTarget);
77
       this.memorialList.push(memorialTarget);
70
     });
78
     });
83
     this.createMemorialList();
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
   formatDeathDate(deathDate: string): string {
113
   formatDeathDate(deathDate: string): string {
87
     const [, month, day] = deathDate.split('-').map(Number);
114
     const [, month, day] = deathDate.split('-').map(Number);
88
     if (!month || !day) {
115
     if (!month || !day) {

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

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

Laden…
Abbrechen
Speichern