poohr 3 недель назад
Родитель
Сommit
e476d2aa58

+ 5
- 0
src/app/app.routes.ts Просмотреть файл

8
 import { MemorialList } from './pages/memorial-list/memorial-list';
8
 import { MemorialList } from './pages/memorial-list/memorial-list';
9
 import { Search } from './pages/search/search';
9
 import { Search } from './pages/search/search';
10
 import { FamilyTree } from './pages/family-tree/family-tree';
10
 import { FamilyTree } from './pages/family-tree/family-tree';
11
+import { EventPage } from './pages/event/event';
11
 
12
 
12
 export const routes: Routes = [
13
 export const routes: Routes = [
13
   {
14
   {
54
     path: 'memorial-list',
55
     path: 'memorial-list',
55
     component: MemorialList,
56
     component: MemorialList,
56
   },
57
   },
58
+  {
59
+    path: 'event',
60
+    component: EventPage,
61
+  },
57
   {
62
   {
58
     path: 'search',
63
     path: 'search',
59
     component: Search,
64
     component: Search,

+ 3
- 0
src/app/models/danka.ts Просмотреть файл

2
 export interface Danka {
2
 export interface Danka {
3
   id: string;
3
   id: string;
4
   householdName: string;
4
   householdName: string;
5
+  householdFurigana: string;
5
   householder: string;
6
   householder: string;
7
+  householderFurigana: string;
6
   postalCode: string;
8
   postalCode: string;
7
   address: string;
9
   address: string;
10
+  note: string;
8
   phones: Phone[];
11
   phones: Phone[];
9
   updatedAt: string;
12
   updatedAt: string;
10
 }
13
 }

+ 17
- 0
src/app/models/event.ts Просмотреть файл

1
+export type EventType = '稚児行列' | '七五三' | '成人式' | '米寿';
2
+
3
+export type EventStatus = '案内済' | '未案内';
4
+
5
+export interface EventTarget {
6
+  id: string;
7
+  dankaId: string;
8
+  name: string;
9
+  furigana: string;
10
+  householdName: string;
11
+  relationship: string;
12
+  birthDate: string;
13
+  age: number;
14
+  eventType: EventType;
15
+  note: string;
16
+  status: EventStatus;
17
+}

+ 1
- 0
src/app/models/memorial.ts Просмотреть файл

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

+ 205
- 32
src/app/pages/danka-detail/danka-detail.scss Просмотреть файл

86
   margin-top: 36px;
86
   margin-top: 36px;
87
   border: 2px solid #8a6543;
87
   border: 2px solid #8a6543;
88
   border-radius: 6px;
88
   border-radius: 6px;
89
-  background: #8a6543;
90
-  color: #ffffff;
89
+  background: #ffffff;
90
+  color: #8a6543;
91
   font-size: 18px;
91
   font-size: 18px;
92
   font-weight: 800;
92
   font-weight: 800;
93
   cursor: pointer;
93
   cursor: pointer;
95
 }
95
 }
96
 
96
 
97
 .edit-button:hover {
97
 .edit-button:hover {
98
-  background: #765639;
98
+  background: #f6efe6;
99
 }
99
 }
100
 
100
 
101
 .family-summary {
101
 .family-summary {
102
-  min-height: 82px;
102
+  min-height: 64px;
103
   margin-bottom: 28px;
103
   margin-bottom: 28px;
104
-  padding: 14px 26px;
104
+  padding: 12px 22px;
105
   border: 2px solid #d8caba;
105
   border: 2px solid #d8caba;
106
   border-radius: 14px;
106
   border-radius: 14px;
107
   background: #eadfce;
107
   background: #eadfce;
108
-  display: grid;
109
-  grid-template-columns: 1.2fr 1.6fr auto;
108
+  display: flex;
110
   align-items: center;
109
   align-items: center;
111
-  column-gap: 24px;
112
   box-sizing: border-box;
110
   box-sizing: border-box;
113
 }
111
 }
114
 
112
 
113
+.family-name-area {
114
+  display: flex;
115
+  align-items: baseline;
116
+  gap: 18px;
117
+}
118
+
115
 .family-name {
119
 .family-name {
116
   margin: 0;
120
   margin: 0;
117
   color: #2f2720;
121
   color: #2f2720;
121
 }
125
 }
122
 
126
 
123
 .family-head {
127
 .family-head {
124
-  margin: 2px 0 0;
128
+  margin: 0;
125
   color: #6f6257;
129
   color: #6f6257;
126
   font-size: 16px;
130
   font-size: 16px;
127
 }
131
 }
153
   width: 140px;
157
   width: 140px;
154
   height: 46px;
158
   height: 46px;
155
   margin-top: 36px;
159
   margin-top: 36px;
156
-  border: 2px solid #d8caba;
160
+  border: 2px solid #8a6543;
157
   border-radius: 6px;
161
   border-radius: 6px;
158
   background: #ffffff;
162
   background: #ffffff;
159
-  color: #2f2720;
160
-  font-size: 16px;
163
+  color: #8a6543;
164
+  font-size: 18px;
161
   font-weight: 800;
165
   font-weight: 800;
162
   cursor: pointer;
166
   cursor: pointer;
163
   box-sizing: border-box;
167
   box-sizing: border-box;
167
   background: #f6efe6;
171
   background: #f6efe6;
168
 }
172
 }
169
 
173
 
174
+.family-page-add-button {
175
+  width: 140px;
176
+  height: 46px;
177
+  margin-top: 36px;
178
+  border: 2px solid #8a6543;
179
+  border-radius: 6px;
180
+  background: #ffffff;
181
+  color: #8a6543;
182
+  font-size: 18px;
183
+  font-weight: 800;
184
+  text-decoration: none;
185
+  display: flex;
186
+  align-items: center;
187
+  justify-content: center;
188
+  box-sizing: border-box;
189
+}
190
+
191
+.family-page-add-button:hover {
192
+  background: #f6efe6;
193
+}
194
+
170
 
195
 
171
 .detail-content {
196
 .detail-content {
172
   display: grid;
197
   display: grid;
173
-  grid-template-columns: minmax(0, 1fr) 500px;
174
-  gap: 48px;
198
+  grid-template-columns: minmax(0, 1fr) 460px;
199
+  gap: 28px;
175
   align-items: start;
200
   align-items: start;
176
 }
201
 }
177
 
202
 
178
 .basic-info-section {
203
 .basic-info-section {
179
-  padding-left: 8px;
204
+  padding-left: 0;
180
 }
205
 }
181
 
206
 
182
 .section-heading {
207
 .section-heading {
197
 }
222
 }
198
 
223
 
199
 .info-form {
224
 .info-form {
200
-  width: 650px;
225
+  width: 100%;
226
+}
227
+
228
+.info-pair-row {
229
+  display: grid;
230
+  grid-template-columns: 1fr 1fr;
231
+  gap: 14px;
232
+  margin-top: 10px;
201
 }
233
 }
202
 
234
 
203
 .info-row {
235
 .info-row {
204
   display: grid;
236
   display: grid;
205
-  grid-template-columns: 120px 1fr;
237
+  grid-template-columns: 96px 1fr;
206
   align-items: center;
238
   align-items: center;
239
+  margin-top: 0;
240
+}
241
+
242
+.info-form > .info-row {
207
   margin-top: 10px;
243
   margin-top: 10px;
208
 }
244
 }
209
 
245
 
228
 
264
 
229
 .phone-row {
265
 .phone-row {
230
   align-items: start;
266
   align-items: start;
231
-  margin-top: 18px;
232
 }
267
 }
233
 
268
 
234
 .phone-row .info-label {
269
 .phone-row .info-label {
242
 .phone-header,
277
 .phone-header,
243
 .phone-item {
278
 .phone-item {
244
   display: grid;
279
   display: grid;
245
-  grid-template-columns: 1fr 1.4fr 1.2fr;
280
+  grid-template-columns: 1fr 1fr;
246
   align-items: center;
281
   align-items: center;
247
 }
282
 }
248
 
283
 
272
 
307
 
273
 .status-panel {
308
 .status-panel {
274
   min-height: 382px;
309
   min-height: 382px;
275
-  padding: 30px 24px 22px;
310
+  padding: 24px 22px 22px;
276
   border: 2px solid #d8caba;
311
   border: 2px solid #d8caba;
277
   border-radius: 62px;
312
   border-radius: 62px;
278
   background: #fffdf9;
313
   background: #fffdf9;
421
   justify-content: space-between;
456
   justify-content: space-between;
422
   align-items: center;
457
   align-items: center;
423
   gap: 16px;
458
   gap: 16px;
424
-  padding: 16px 20px;
459
+  padding: 14px 18px;
425
   margin-bottom: 20px;
460
   margin-bottom: 20px;
426
   background: #efe4d6;
461
   background: #efe4d6;
427
   border: 2px solid #d8c2aa;
462
   border: 2px solid #d8c2aa;
428
   border-radius: 12px;
463
   border-radius: 12px;
429
 }
464
 }
430
 
465
 
431
-.family-list-title {
432
-  margin: 0;
433
-  font-size: 20px;
434
-  font-weight: 700;
466
+.family-search-box {
467
+  flex: 1;
468
+  min-width: 0;
469
+  display: grid;
470
+  grid-template-columns: 96px minmax(0, 1fr);
471
+  gap: 12px;
472
+  align-items: center;
435
 }
473
 }
436
 
474
 
437
-.family-list-head {
438
-  margin: 0;
439
-  font-size: 14px;
440
-  color: #6f6256;
475
+.family-search-label {
476
+  color: #4b3c31;
477
+  font-size: 16px;
478
+  font-weight: 800;
479
+}
480
+
481
+.family-search-box input {
482
+  width: 100%;
483
+  height: 42px;
484
+  padding: 0 14px;
485
+  border: 2px solid #d8c2aa;
486
+  border-radius: 8px;
487
+  background: #fffdf9;
488
+  color: #2f2720;
489
+  font-size: 15px;
490
+  font-weight: 700;
491
+  box-sizing: border-box;
441
 }
492
 }
442
 
493
 
443
 .family-table-section {
494
 .family-table-section {
483
   background: #f6efe6;
534
   background: #f6efe6;
484
 }
535
 }
485
 
536
 
537
+.event-status-select {
538
+  width: 84px;
539
+  height: 34px;
540
+  padding: 0 8px;
541
+  border: 2px solid #d8c2aa;
542
+  border-radius: 6px;
543
+  background: #ffffff;
544
+  color: #6f6256;
545
+  font-size: 13px;
546
+  font-weight: 800;
547
+  cursor: pointer;
548
+  box-sizing: border-box;
549
+  outline: none;
550
+}
551
+
552
+.event-status-select.sent {
553
+  border-color: #8a6543;
554
+  background: #f1e7d8;
555
+  color: #8a6543;
556
+}
557
+
558
+.event-status-select:focus {
559
+  border-color: #8a6543;
560
+  box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.15);
561
+}
562
+
486
 .family-table-header {
563
 .family-table-header {
487
   padding: 12px 14px;
564
   padding: 12px 14px;
488
   background: #efe4d6;
565
   background: #efe4d6;
490
   border-radius: 10px;
567
   border-radius: 10px;
491
   font-size: 14px;
568
   font-size: 14px;
492
   font-weight: 700;
569
   font-weight: 700;
493
-  color: #5d3b24;
570
+  color: #111111;
494
 }
571
 }
495
 
572
 
496
 .family-table-row {
573
 .family-table-row {
500
   border: 2px solid #d8c2aa;
577
   border: 2px solid #d8c2aa;
501
   border-radius: 10px;
578
   border-radius: 10px;
502
   font-size: 15px;
579
   font-size: 15px;
503
-  color: #2f2923;
580
+  color: #111111;
504
 }
581
 }
505
 
582
 
506
 .family-table-row:hover {
583
 .family-table-row:hover {
512
   margin: 0;
589
   margin: 0;
513
   font-size: 15px;
590
   font-size: 15px;
514
   font-weight: 700;
591
   font-weight: 700;
515
-  color: #5d3b24;
592
+  color: #111111;
593
+}
594
+
595
+.family-member-table {
596
+  gap: 0;
597
+  border: 2px solid #d8c2aa;
598
+  border-radius: 8px;
599
+  overflow: hidden;
600
+}
601
+
602
+.family-member-table .family-table-header,
603
+.family-member-table .family-table-row {
604
+  grid-template-columns: 1.35fr 0.75fr 1.05fr 1fr 1.2fr 168px;
605
+  gap: 12px;
606
+  border: 0;
607
+  border-radius: 0;
608
+}
609
+
610
+.family-member-table .family-table-header {
611
+  min-height: 46px;
612
+  padding: 0 14px;
613
+  background: #efe4d6;
614
+}
615
+
616
+.family-member-table .family-table-row {
617
+  min-height: 78px;
618
+  padding: 10px 14px;
619
+  background: #fffdf9;
620
+  border-top: 1px solid #d8c2aa;
621
+}
622
+
623
+.family-member-table .family-table-row:hover {
624
+  background: #fff8ee;
625
+}
626
+
627
+.family-person-sub,
628
+.family-person-date {
629
+  margin: 0;
630
+}
631
+
632
+.family-person-sub {
633
+  margin-top: 4px;
634
+  color: #111111;
635
+  font-size: 13px;
636
+  line-height: 1.35;
637
+}
638
+
639
+.family-person-date {
640
+  color: #111111;
641
+  font-size: 15px;
642
+}
643
+
644
+.family-member-table .family-table-action {
645
+  justify-content: center;
646
+  gap: 8px;
647
+}
648
+
649
+.family-member-table .family-table-header > div:last-child {
650
+  text-align: center;
651
+}
652
+
653
+.family-member-table .family-edit-link {
654
+  width: 74px;
655
+}
656
+
657
+.kakocho-member-table .family-table-header,
658
+.kakocho-member-table .family-table-row {
659
+  grid-template-columns: 1.25fr 1.15fr 1.05fr 0.65fr 0.6fr 1.1fr 82px;
660
+}
661
+
662
+.event-member-table .family-table-header,
663
+.event-member-table .family-table-row {
664
+  grid-template-columns: 1.35fr 1fr 0.75fr 1.05fr 0.95fr 96px;
665
+}
666
+
667
+.event-member-table .event-status-select {
668
+  width: 90px;
669
+}
670
+
671
+.event-member-table .family-table-header > div:last-child,
672
+.event-member-table .family-table-row > div:last-child {
673
+  text-align: center;
516
 }
674
 }
517
 
675
 
518
 .empty-family-message {
676
 .empty-family-message {
556
   margin-top: 24px;
714
   margin-top: 24px;
557
 }
715
 }
558
 
716
 
717
+.coming-soon-section .section-heading {
718
+  margin-bottom: 16px;
719
+
720
+  h2 {
721
+    margin: 0;
722
+    font-size: 20px;
723
+    font-weight: 700;
724
+  }
725
+}
726
+
559
 /* =========================
727
 /* =========================
560
    家系図タブ
728
    家系図タブ
561
 ========================= */
729
 ========================= */
1023
     flex-direction: column;
1191
     flex-direction: column;
1024
   }
1192
   }
1025
 
1193
 
1194
+  .family-search-box {
1195
+    width: 100%;
1196
+    grid-template-columns: 1fr;
1197
+  }
1198
+
1026
   .family-table {
1199
   .family-table {
1027
     overflow-x: auto;
1200
     overflow-x: auto;
1028
   }
1201
   }

+ 32
- 4
src/app/pages/danka-edit/danka-edit.html Просмотреть файл

17
 
17
 
18
             <div class="form-list">
18
             <div class="form-list">
19
               <div class="form-row">
19
               <div class="form-row">
20
-                <label for="householdName">世帯名</label>
20
+                <label for="householdName">檀家名</label>
21
                 <div class="form-field">
21
                 <div class="form-field">
22
                   <input
22
                   <input
23
                     id="householdName"
23
                     id="householdName"
25
                     formControlName="householdName"
25
                     formControlName="householdName"
26
                   />
26
                   />
27
                   @if (dankaForm.get('householdName')?.invalid && dankaForm.get('householdName')?.touched) {
27
                   @if (dankaForm.get('householdName')?.invalid && dankaForm.get('householdName')?.touched) {
28
-                    <p class="error-message">世帯名を入力してください。</p>
28
+                    <p class="error-message">檀家名を入力してください。</p>
29
                   }
29
                   }
30
                 </div>
30
                 </div>
31
               </div>
31
               </div>
32
 
32
 
33
               <div class="form-row">
33
               <div class="form-row">
34
-                <label for="householder">世帯主</label>
34
+                <label for="householdFurigana">檀家名ふりがな</label>
35
+                <input
36
+                  id="householdFurigana"
37
+                  type="text"
38
+                  formControlName="householdFurigana"
39
+                />
40
+              </div>
41
+
42
+              <div class="form-row">
43
+                <label for="householder">施主名</label>
35
                 <div class="form-field">
44
                 <div class="form-field">
36
                   <input
45
                   <input
37
                     id="householder"
46
                     id="householder"
39
                     formControlName="householder"
48
                     formControlName="householder"
40
                   />
49
                   />
41
                   @if (dankaForm.get('householder')?.invalid && dankaForm.get('householder')?.touched) {
50
                   @if (dankaForm.get('householder')?.invalid && dankaForm.get('householder')?.touched) {
42
-                    <p class="error-message">世帯主を入力してください。</p>
51
+                    <p class="error-message">施主名を入力してください。</p>
43
                   }
52
                   }
44
                 </div>
53
                 </div>
45
               </div>
54
               </div>
46
 
55
 
56
+              <div class="form-row">
57
+                <label for="householderFurigana">施主名ふりがな</label>
58
+                <input
59
+                  id="householderFurigana"
60
+                  type="text"
61
+                  formControlName="householderFurigana"
62
+                />
63
+              </div>
64
+
47
               <div class="form-row">
65
               <div class="form-row">
48
                 <label for="postalCode">郵便番号</label>
66
                 <label for="postalCode">郵便番号</label>
49
                 <div class="form-field">
67
                 <div class="form-field">
66
                   formControlName="address"
84
                   formControlName="address"
67
                 />
85
                 />
68
               </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>
69
             </div>
97
             </div>
70
           </section>
98
           </section>
71
 
99
 

+ 26
- 5
src/app/pages/danka-edit/danka-edit.scss Просмотреть файл

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
 }
93
 
94
 
94
 .form-row {
95
 .form-row {
95
   display: grid;
96
   display: grid;
96
-  grid-template-columns: 120px 1fr;
97
+  grid-template-columns: 160px 1fr;
97
   align-items: center;
98
   align-items: center;
98
   gap: 16px;
99
   gap: 16px;
99
   margin-bottom: 14px;
100
   margin-bottom: 14px;
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
 }

+ 9
- 0
src/app/pages/danka-edit/danka-edit.ts Просмотреть файл

24
 
24
 
25
   dankaForm = new FormGroup({
25
   dankaForm = new FormGroup({
26
     householdName: new FormControl('', [Validators.required]),
26
     householdName: new FormControl('', [Validators.required]),
27
+    householdFurigana: new FormControl(''),
27
     householder: new FormControl('', [Validators.required]),
28
     householder: new FormControl('', [Validators.required]),
29
+    householderFurigana: new FormControl(''),
28
     postalCode: new FormControl('', Validators.pattern(/^\d{3}-\d{4}$/)),
30
     postalCode: new FormControl('', Validators.pattern(/^\d{3}-\d{4}$/)),
29
     address: new FormControl(''),
31
     address: new FormControl(''),
32
+    note: new FormControl(''),
30
     phones: new FormArray([this.createPhoneForm('', '')]),
33
     phones: new FormArray([this.createPhoneForm('', '')]),
31
   });
34
   });
32
 
35
 
42
       if (this.danka) {
45
       if (this.danka) {
43
         this.dankaForm.patchValue({
46
         this.dankaForm.patchValue({
44
           householdName: this.danka.householdName,
47
           householdName: this.danka.householdName,
48
+          householdFurigana: this.danka.householdFurigana,
45
           householder: this.danka.householder,
49
           householder: this.danka.householder,
50
+          householderFurigana: this.danka.householderFurigana,
46
           postalCode: this.danka.postalCode,
51
           postalCode: this.danka.postalCode,
47
           address: this.danka.address,
52
           address: this.danka.address,
53
+          note: this.danka.note,
48
         });
54
         });
49
 
55
 
50
         this.phones.clear();
56
         this.phones.clear();
90
     const updatedDanka: Danka = {
96
     const updatedDanka: Danka = {
91
       id: dankaId,
97
       id: dankaId,
92
       householdName: formValue.householdName?.trim() ?? '',
98
       householdName: formValue.householdName?.trim() ?? '',
99
+      householdFurigana: formValue.householdFurigana?.trim() ?? '',
93
       householder: formValue.householder?.trim() ?? '',
100
       householder: formValue.householder?.trim() ?? '',
101
+      householderFurigana: formValue.householderFurigana?.trim() ?? '',
94
       postalCode: formValue.postalCode?.trim() ?? '',
102
       postalCode: formValue.postalCode?.trim() ?? '',
95
       address: formValue.address?.trim() ?? '',
103
       address: formValue.address?.trim() ?? '',
104
+      note: formValue.note?.trim() ?? '',
96
       updatedAt: this.formatDateForSave(new Date()),
105
       updatedAt: this.formatDateForSave(new Date()),
97
       phones: (formValue.phones ?? [])
106
       phones: (formValue.phones ?? [])
98
         .map((phone) => ({
107
         .map((phone) => ({

+ 10
- 6
src/app/pages/danka-list/danka-list.html Просмотреть файл

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>家族</div>
59
           </div>
58
           </div>
60
 
59
 
61
           @if (filterDankaList.length === 0) {
60
           @if (filterDankaList.length === 0) {
64
 
63
 
65
           @for (danka of filterDankaList; track danka.id) {
64
           @for (danka of filterDankaList; track danka.id) {
66
             <a class="danka-table-row" [routerLink]="['/danka-detail', danka.id]">
65
             <a class="danka-table-row" [routerLink]="['/danka-detail', danka.id]">
67
-              <div class="strong">{{ danka.householder }}</div>
68
-              <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>
69
               <div>{{ danka.address }}</div>
74
               <div>{{ danka.address }}</div>
70
               <div>{{ danka.phones[0]?.tel }}</div>
75
               <div>{{ danka.phones[0]?.tel }}</div>
71
-              <div>{{ danka.phones.length }}件</div>
72
             </a>
76
             </a>
73
           }
77
           }
74
         </div>
78
         </div>

+ 36
- 41
src/app/pages/danka-list/danka-list.scss Просмотреть файл

43
 
43
 
44
 .search-area {
44
 .search-area {
45
   display: grid;
45
   display: grid;
46
-  grid-template-columns: 1fr 140px 190px 110px;
46
+  grid-template-columns: 1fr 140px 140px;
47
   gap: 18px;
47
   gap: 18px;
48
   align-items: center;
48
   align-items: center;
49
   margin-bottom: 18px;
49
   margin-bottom: 18px;
90
 .new-button,
90
 .new-button,
91
 .condition-button {
91
 .condition-button {
92
   height: 58px;
92
   height: 58px;
93
-  border: 2px solid #d8caba;
93
+  border: 2px solid #8a6543;
94
   border-radius: 8px;
94
   border-radius: 8px;
95
-  color: #2f2720;
95
+  background: #ffffff;
96
+  color: #8a6543;
96
   font-size: 18px;
97
   font-size: 18px;
97
   font-weight: 800;
98
   font-weight: 800;
98
   cursor: pointer;
99
   cursor: pointer;
99
   box-sizing: border-box;
100
   box-sizing: border-box;
100
 }
101
 }
101
 
102
 
102
-.search-button {
103
-  background: #8a6543;
104
-  border-color: #8a6543;
105
-  color: #ffffff;
106
-}
107
-
108
-.new-button {
109
-  background: #e6d8c4;
110
-}
111
-
112
 .condition-button {
103
 .condition-button {
113
   background: #ffffff;
104
   background: #ffffff;
114
 }
105
 }
115
 
106
 
116
-.search-button:hover {
117
-  background: #765639;
118
-}
119
-
107
+.search-button:hover,
120
 .new-button:hover,
108
 .new-button:hover,
121
 .condition-button:hover {
109
 .condition-button:hover {
122
   background: #f6efe6;
110
   background: #f6efe6;
178
 
166
 
179
 .list-content {
167
 .list-content {
180
   position: relative;
168
   position: relative;
181
-  padding-right: 172px;
182
 }
169
 }
183
 
170
 
184
 .danka-table {
171
 .danka-table {
185
   width: 100%;
172
   width: 100%;
173
+  display: grid;
174
+  gap: 0;
175
+  border: 2px solid #d8c2aa;
176
+  border-radius: 8px;
177
+  overflow: hidden;
186
 }
178
 }
187
 
179
 
188
 .danka-table-header,
180
 .danka-table-header,
189
 .danka-table-row {
181
 .danka-table-row {
190
   display: grid;
182
   display: grid;
191
-  grid-template-columns: 1.5fr 1.1fr 2.8fr 1.25fr 0.65fr;
183
+  grid-template-columns: 1.2fr 1.2fr 2.2fr 1fr;
192
   align-items: center;
184
   align-items: center;
193
-  column-gap: 16px;
185
+  justify-items: start;
186
+  gap: 12px;
187
+  text-align: left;
194
 }
188
 }
195
 
189
 
196
 .danka-table-header {
190
 .danka-table-header {
197
-  min-height: 40px;
198
-  padding: 0 12px;
199
-  background: #eadfce;
200
-  border: 2px solid #d8caba;
201
-  border-radius: 6px;
191
+  min-height: 46px;
192
+  padding: 0 14px;
193
+  background: #efe4d6;
202
   box-sizing: border-box;
194
   box-sizing: border-box;
203
-  color: #5a4a3c;
204
-  font-size: 15px;
205
-  font-weight: 700;
195
+  color: #111111;
196
+  font-size: 14px;
197
+  font-weight: 800;
206
 }
198
 }
207
 
199
 
208
 .danka-table-row {
200
 .danka-table-row {
209
-  min-height: 66px;
210
-  margin-top: 4px;
211
-  padding: 0 12px;
212
-  background: #fbf7f1;
213
-  border: 2px solid #d8caba;
214
-  border-radius: 8px;
201
+  min-height: 78px;
202
+  padding: 10px 14px;
203
+  background: #fffdf9;
204
+  border-top: 1px solid #d8c2aa;
215
   box-sizing: border-box;
205
   box-sizing: border-box;
216
-  color: #2f2720;
217
-  font-size: 16px;
206
+  color: #111111;
207
+  font-size: 15px;
218
   text-decoration: none;
208
   text-decoration: none;
219
 }
209
 }
220
 
210
 
221
 .danka-table-row:hover {
211
 .danka-table-row:hover {
222
-  background: #f3eadc;
212
+  background: #fff8ee;
223
 }
213
 }
224
 
214
 
225
-.danka-table-row .strong {
215
+.danka-name {
216
+  margin: 0;
226
   font-weight: 800;
217
   font-weight: 800;
227
 }
218
 }
228
 
219
 
220
+.danka-sub {
221
+  margin: 4px 0 0;
222
+  color: #111111;
223
+  font-size: 13px;
224
+  line-height: 1.35;
225
+}
226
+
229
 .empty-message {
227
 .empty-message {
230
-  margin-top: 16px;
231
   padding: 24px;
228
   padding: 24px;
232
-  border: 2px dashed #d8caba;
233
-  border-radius: 12px;
234
   background: #fffdf9;
229
   background: #fffdf9;
235
   color: #7b6b5c;
230
   color: #7b6b5c;
236
   font-size: 16px;
231
   font-size: 16px;

+ 2
- 0
src/app/pages/danka-list/danka-list.ts Просмотреть файл

105
     this.filterDankaList = this.dankaList.filter((danka) => {
105
     this.filterDankaList = this.dankaList.filter((danka) => {
106
       return (
106
       return (
107
         danka.householdName.includes(keyword) ||
107
         danka.householdName.includes(keyword) ||
108
+        danka.householdFurigana.includes(keyword) ||
108
         danka.householder.includes(keyword) ||
109
         danka.householder.includes(keyword) ||
110
+        danka.householderFurigana.includes(keyword) ||
109
         danka.postalCode.includes(keyword) ||
111
         danka.postalCode.includes(keyword) ||
110
         danka.address.includes(keyword) ||
112
         danka.address.includes(keyword) ||
111
         danka.phones.some((phone) => phone.tel.includes(keyword) || phone.note.includes(keyword))
113
         danka.phones.some((phone) => phone.tel.includes(keyword) || phone.note.includes(keyword))

+ 1
- 1
src/app/pages/dashboard/dashboard.html Просмотреть файл

51
 
51
 
52
         <div class="recent-table" role="table" aria-label="最近開いた檀家">
52
         <div class="recent-table" role="table" aria-label="最近開いた檀家">
53
           <div class="recent-row recent-row-head" role="row">
53
           <div class="recent-row recent-row-head" role="row">
54
-            <div class="cell" role="columnheader">世帯主</div>
54
+            <div class="cell" role="columnheader">施主名</div>
55
             <div class="cell" role="columnheader">ふりがな</div>
55
             <div class="cell" role="columnheader">ふりがな</div>
56
             <div class="cell" role="columnheader">住所</div>
56
             <div class="cell" role="columnheader">住所</div>
57
             <div class="cell" role="columnheader">次の法要</div>
57
             <div class="cell" role="columnheader">次の法要</div>

+ 130
- 0
src/app/pages/event/event.html Просмотреть файл

1
+<app-header></app-header>
2
+
3
+<div class="event-page">
4
+  <app-side-menu></app-side-menu>
5
+
6
+  <main class="event-main">
7
+    <section class="event-panel">
8
+      <div class="page-title-row">
9
+        <div class="title-filter-area">
10
+          <h1>行事対象者一覧</h1>
11
+
12
+          <div class="filter-row">
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>
24
+            </div>
25
+
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
+              />
56
+            </div>
57
+          </div>
58
+        </div>
59
+      </div>
60
+
61
+      <div class="list-header-row">
62
+        <h2>対象 {{ filteredEventTargets.length }} 名</h2>
63
+
64
+        <p>並び順: 行事 / 年齢 / 氏名</p>
65
+      </div>
66
+
67
+      <section class="event-table-section">
68
+        <div class="event-table">
69
+          <div class="event-table-header">
70
+            <div>氏名・ふりがな</div>
71
+            <div>檀家名</div>
72
+            <div>続柄</div>
73
+            <div>生年月日・年齢</div>
74
+            <div>対象行事</div>
75
+            <div>備考</div>
76
+            <div>状態</div>
77
+          </div>
78
+
79
+          @if (filteredEventTargets.length > 0) {
80
+            @for (target of filteredEventTargets; track target.id) {
81
+              <div class="event-table-row">
82
+                <div>
83
+                  <p class="person-name">{{ target.name }}</p>
84
+                  <p class="person-sub">{{ target.furigana || 'ふりがな未登録' }}</p>
85
+                </div>
86
+                <div>
87
+                  {{ target.householdName }}
88
+                </div>
89
+                <div>
90
+                  {{ target.relationship }}
91
+                </div>
92
+                <div>
93
+                  <p class="person-date">{{ target.birthDate }}</p>
94
+                  <p class="person-sub">{{ target.age }}歳</p>
95
+                </div>
96
+                <div class="event-type">
97
+                  {{ target.eventType }}
98
+                </div>
99
+                <div>
100
+                  {{ target.note || '' }}
101
+                </div>
102
+                <div>
103
+                  <select
104
+                    class="status-select"
105
+                    [class.sent]="target.status === '案内済'"
106
+                    [ngModel]="target.status"
107
+                    (ngModelChange)="changeStatus(target, $event)"
108
+                    aria-label="状態"
109
+                  >
110
+                    @for (status of eventStatuses; track status) {
111
+                      <option [value]="status">{{ status }}</option>
112
+                    }
113
+                  </select>
114
+                </div>
115
+              </div>
116
+            }
117
+          } @else {
118
+            <div class="empty-message">
119
+              @if (eventTargets.length > 0) {
120
+                検索条件に一致する行事対象者はありません。
121
+              } @else {
122
+                対象となる行事対象者はありません。
123
+              }
124
+            </div>
125
+          }
126
+        </div>
127
+      </section>
128
+    </section>
129
+  </main>
130
+</div>

+ 287
- 0
src/app/pages/event/event.scss Просмотреть файл

1
+:host {
2
+  position: relative;
3
+  display: block;
4
+  min-height: 100vh;
5
+  background: #f4eee4;
6
+  color: #2f2720;
7
+}
8
+
9
+.event-page {
10
+  display: flex;
11
+  align-items: flex-start;
12
+  gap: 8px;
13
+  background: #f4eee4;
14
+}
15
+
16
+.event-main {
17
+  flex: 1;
18
+  padding-right: 34px;
19
+  box-sizing: border-box;
20
+}
21
+
22
+.event-panel {
23
+  min-height: 650px;
24
+  padding: 26px 34px 36px;
25
+  background: #ffffff;
26
+  border: 2px solid #d8caba;
27
+  border-radius: 76px;
28
+  box-sizing: border-box;
29
+}
30
+
31
+.page-title-row {
32
+  display: flex;
33
+  justify-content: space-between;
34
+  align-items: flex-start;
35
+  gap: 24px;
36
+  margin-bottom: 22px;
37
+}
38
+
39
+.title-filter-area {
40
+  display: grid;
41
+  gap: 12px;
42
+  min-width: 0;
43
+}
44
+
45
+.page-title-row h1 {
46
+  margin: 0;
47
+  color: #2f2720;
48
+  font-size: 32px;
49
+  line-height: 1.2;
50
+  font-weight: 800;
51
+  letter-spacing: 0.02em;
52
+}
53
+
54
+.filter-row {
55
+  display: flex;
56
+  align-items: flex-end;
57
+  gap: 14px 18px;
58
+  flex-wrap: wrap;
59
+  width: 100%;
60
+}
61
+
62
+.filter-field,
63
+.search-field {
64
+  display: grid;
65
+  gap: 6px;
66
+}
67
+
68
+.filter-field label,
69
+.search-field label {
70
+  color: #4b3c31;
71
+  font-size: 14px;
72
+  font-weight: 800;
73
+}
74
+
75
+.filter-field select,
76
+.search-field input {
77
+  height: 38px;
78
+  padding: 0 14px;
79
+  border: 2px solid #d8caba;
80
+  border-radius: 8px;
81
+  background: #fffdf9;
82
+  color: #2f2720;
83
+  font-size: 15px;
84
+  font-weight: 700;
85
+  box-sizing: border-box;
86
+  outline: none;
87
+}
88
+
89
+.filter-field select {
90
+  width: 148px;
91
+  cursor: pointer;
92
+}
93
+
94
+.search-field {
95
+  flex: 1 1 260px;
96
+  min-width: 260px;
97
+}
98
+
99
+.search-field input {
100
+  width: 100%;
101
+}
102
+
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);
107
+}
108
+
109
+.list-header-row {
110
+  display: flex;
111
+  justify-content: space-between;
112
+  align-items: flex-end;
113
+  margin: 18px 0 10px;
114
+}
115
+
116
+.list-header-row h2 {
117
+  margin: 0;
118
+  color: #2f2720;
119
+  font-size: 26px;
120
+  font-weight: 800;
121
+}
122
+
123
+.list-header-row p {
124
+  margin: 0;
125
+  color: #7b6b5c;
126
+  font-size: 14px;
127
+}
128
+
129
+.event-table-section {
130
+  margin-top: 8px;
131
+}
132
+
133
+.event-table {
134
+  display: grid;
135
+  gap: 0;
136
+  border: 2px solid #d8c2aa;
137
+  border-radius: 8px;
138
+  overflow: hidden;
139
+}
140
+
141
+.event-table-header,
142
+.event-table-row {
143
+  display: grid;
144
+  grid-template-columns: 1.25fr 0.95fr 0.68fr 0.95fr 0.82fr 1fr 96px;
145
+  align-items: center;
146
+  gap: 12px;
147
+}
148
+
149
+.event-table-header {
150
+  min-height: 46px;
151
+  padding: 0 14px;
152
+  background: #efe4d6;
153
+  color: #111111;
154
+  font-size: 14px;
155
+  font-weight: 800;
156
+  box-sizing: border-box;
157
+}
158
+
159
+.event-table-row {
160
+  min-height: 78px;
161
+  padding: 10px 14px;
162
+  border-top: 1px solid #d8c2aa;
163
+  background: #fffdf9;
164
+  color: #111111;
165
+  font-size: 15px;
166
+  box-sizing: border-box;
167
+}
168
+
169
+.event-table-row:hover {
170
+  background: #fff8ee;
171
+}
172
+
173
+.person-name,
174
+.event-type {
175
+  margin: 0;
176
+  font-weight: 800;
177
+}
178
+
179
+.person-sub,
180
+.person-date {
181
+  margin: 0;
182
+}
183
+
184
+.person-sub {
185
+  margin-top: 4px;
186
+  color: #111111;
187
+  font-size: 13px;
188
+  line-height: 1.35;
189
+}
190
+
191
+.person-date {
192
+  color: #111111;
193
+  font-size: 15px;
194
+}
195
+
196
+.event-type {
197
+  color: #111111;
198
+}
199
+
200
+.status-select {
201
+  width: 90px;
202
+  height: 34px;
203
+  padding: 0 10px;
204
+  border: 2px solid #d8caba;
205
+  border-radius: 8px;
206
+  background: #ffffff;
207
+  color: #111111;
208
+  font-size: 14px;
209
+  font-weight: 800;
210
+  box-sizing: border-box;
211
+  cursor: pointer;
212
+  outline: none;
213
+}
214
+
215
+.status-select.sent {
216
+  border-color: #8a6543;
217
+  background: #f1e7d8;
218
+  color: #111111;
219
+}
220
+
221
+.status-select:focus {
222
+  border-color: #8a6543;
223
+  box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.15);
224
+}
225
+
226
+.empty-message {
227
+  min-height: 58px;
228
+  padding: 18px 20px;
229
+  background: #fffdf9;
230
+  color: #7b6b5c;
231
+  font-size: 15px;
232
+  font-weight: 700;
233
+  box-sizing: border-box;
234
+}
235
+
236
+@media (max-width: 1100px) {
237
+  .page-title-row {
238
+    flex-direction: column;
239
+  }
240
+
241
+  .event-table {
242
+    overflow-x: auto;
243
+  }
244
+
245
+  .event-table-header,
246
+  .event-table-row {
247
+    min-width: 980px;
248
+  }
249
+}
250
+
251
+@media (max-width: 800px) {
252
+  .event-page {
253
+    flex-direction: column;
254
+  }
255
+
256
+  .event-main {
257
+    width: 100%;
258
+    padding: 16px 20px 32px;
259
+  }
260
+
261
+  .event-panel {
262
+    padding: 24px 20px 30px;
263
+    border-radius: 32px;
264
+  }
265
+
266
+  .page-title-row h1 {
267
+    font-size: 26px;
268
+  }
269
+
270
+  .filter-row {
271
+    align-items: flex-start;
272
+    flex-direction: column;
273
+    gap: 14px;
274
+  }
275
+
276
+  .filter-field,
277
+  .filter-field select,
278
+  .search-field {
279
+    width: 100%;
280
+  }
281
+
282
+  .list-header-row {
283
+    align-items: flex-start;
284
+    flex-direction: column;
285
+    gap: 8px;
286
+  }
287
+}

+ 22
- 0
src/app/pages/event/event.spec.ts Просмотреть файл

1
+import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { EventPage } from './event';
4
+
5
+describe('EventPage', () => {
6
+  let component: EventPage;
7
+  let fixture: ComponentFixture<EventPage>;
8
+
9
+  beforeEach(async () => {
10
+    await TestBed.configureTestingModule({
11
+      imports: [EventPage],
12
+    }).compileComponents();
13
+
14
+    fixture = TestBed.createComponent(EventPage);
15
+    component = fixture.componentInstance;
16
+    await fixture.whenStable();
17
+  });
18
+
19
+  it('should create', () => {
20
+    expect(component).toBeTruthy();
21
+  });
22
+});

+ 157
- 0
src/app/pages/event/event.ts Просмотреть файл

1
+import { Component } from '@angular/core';
2
+import { FormsModule } from '@angular/forms';
3
+import { DankaService } from '../../services/dankaService';
4
+import { FamilyService } from '../../services/family-service';
5
+import { EventStatus, EventTarget, EventType } from '../../models/event';
6
+import { AppHeader } from '../../share/header/app-header';
7
+import { AppSideMenu } from '../../share/side-menu/app-side-menu';
8
+
9
+@Component({
10
+  selector: 'app-event',
11
+  imports: [AppHeader, AppSideMenu, FormsModule],
12
+  templateUrl: './event.html',
13
+  styleUrl: './event.scss',
14
+})
15
+export class EventPage {
16
+  eventTargets: EventTarget[] = [];
17
+  targetYear: number = new Date().getFullYear();
18
+  selectedEventType: EventType | 'all' = 'all';
19
+  selectedStatus: EventStatus | 'all' = 'all';
20
+  searchKeyword = '';
21
+  eventStatuses: EventStatus[] = ['未案内', '案内済'];
22
+  yearOptions: number[] = [
23
+    this.targetYear - 1,
24
+    this.targetYear,
25
+    this.targetYear + 1,
26
+    this.targetYear + 2,
27
+  ];
28
+  eventTypeFilters: { label: string; value: EventType | 'all' }[] = [
29
+    { label: 'すべて', value: 'all' },
30
+    { label: '稚児行列', value: '稚児行列' },
31
+    { label: '七五三', value: '七五三' },
32
+    { label: '成人式', value: '成人式' },
33
+    { label: '米寿', value: '米寿' },
34
+  ];
35
+  statusFilters: { label: string; value: EventStatus | 'all' }[] = [
36
+    { label: 'すべて', value: 'all' },
37
+    { label: '未案内', value: '未案内' },
38
+    { label: '案内済', value: '案内済' },
39
+  ];
40
+  private statusByTargetId: Record<string, EventStatus> = {};
41
+
42
+  constructor(
43
+    private dankaService: DankaService,
44
+    private familyService: FamilyService,
45
+  ) {
46
+    this.createEventTargetList();
47
+  }
48
+
49
+  createEventTargetList(): void {
50
+    this.eventTargets = [];
51
+
52
+    this.familyService.getFamilyList().forEach((family) => {
53
+      const birthDate = this.parseDate(family.birthDate);
54
+      if (!birthDate) {
55
+        return;
56
+      }
57
+
58
+      const age = this.targetYear - birthDate.getFullYear();
59
+      const eventTypes = this.getEventTypes(age);
60
+      if (eventTypes.length === 0) {
61
+        return;
62
+      }
63
+
64
+      const danka = this.dankaService.getDankaById(family.dankaId);
65
+      eventTypes.forEach((eventType) => {
66
+        if (this.selectedEventType !== 'all' && this.selectedEventType !== eventType) {
67
+          return;
68
+        }
69
+
70
+        const id = `${family.id}-${eventType}`;
71
+        this.eventTargets.push({
72
+          id,
73
+          dankaId: family.dankaId,
74
+          name: family.name,
75
+          furigana: family.furigana,
76
+          householdName: danka?.householdName ?? '不明',
77
+          relationship: family.relationship || '未登録',
78
+          birthDate: family.birthDate,
79
+          age,
80
+          eventType,
81
+          note: family.note,
82
+          status: this.statusByTargetId[id] ?? (Number(family.id) % 2 === 0 ? '案内済' : '未案内'),
83
+        });
84
+      });
85
+    });
86
+
87
+    this.eventTargets.sort(
88
+      (a, b) =>
89
+        this.getEventSortOrder(a.eventType) - this.getEventSortOrder(b.eventType) ||
90
+        a.age - b.age ||
91
+        a.name.localeCompare(b.name, 'ja'),
92
+    );
93
+  }
94
+
95
+  changeEventType(eventType: EventType | 'all'): void {
96
+    this.selectedEventType = eventType;
97
+    this.createEventTargetList();
98
+  }
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
+
122
+  changeStatus(target: EventTarget, status: EventStatus): void {
123
+    target.status = status;
124
+    this.statusByTargetId[target.id] = status;
125
+  }
126
+
127
+  private getEventTypes(age: number): EventType[] {
128
+    const eventTypes: EventType[] = [];
129
+
130
+    if (age >= 3 && age <= 12) {
131
+      eventTypes.push('稚児行列');
132
+    }
133
+    if ([3, 5, 7].includes(age)) {
134
+      eventTypes.push('七五三');
135
+    }
136
+    if (age === 20) {
137
+      eventTypes.push('成人式');
138
+    }
139
+    if (age === 88) {
140
+      eventTypes.push('米寿');
141
+    }
142
+
143
+    return eventTypes;
144
+  }
145
+
146
+  private getEventSortOrder(eventType: EventType): number {
147
+    return ['稚児行列', '七五三', '成人式', '米寿'].indexOf(eventType);
148
+  }
149
+
150
+  private parseDate(value: string): Date | null {
151
+    const [year, month, day] = value.split('-').map(Number);
152
+    if (!year || !month || !day) {
153
+      return null;
154
+    }
155
+    return new Date(year, month - 1, day);
156
+  }
157
+}

+ 4
- 4
src/app/pages/family-edit/family-edit.html Просмотреть файл

7
   <main class="danka-edit-main">
7
   <main class="danka-edit-main">
8
     <section class="edit-panel">
8
     <section class="edit-panel">
9
       <div class="page-title-area">
9
       <div class="page-title-area">
10
-        <h1>家族(個人)編集</h1>
10
+        <h1>{{ familyId ? '家族編集' : '家族追加' }}</h1>
11
       </div>
11
       </div>
12
 
12
 
13
       <form [formGroup]="familyForm" class="edit-form">
13
       <form [formGroup]="familyForm" class="edit-form">
44
                   <select id="relationship"
44
                   <select id="relationship"
45
                           formControlName="relationship">
45
                           formControlName="relationship">
46
                     <option value="">選択してください</option>
46
                     <option value="">選択してください</option>
47
-                    <option value="世帯主">世帯主</option>
47
+                    <option value="施主">施主</option>
48
                     <option value="配偶者">配偶者</option>
48
                     <option value="配偶者">配偶者</option>
49
                     <option value="父">父</option>
49
                     <option value="父">父</option>
50
                     <option value="母">母</option>
50
                     <option value="母">母</option>
170
 
170
 
171
           <section class="phone-edit-section">
171
           <section class="phone-edit-section">
172
             <div class="householder-area">
172
             <div class="householder-area">
173
-              <h3>この方を世帯主にする</h3>
173
+              <h3>この方を主にする</h3>
174
 
174
 
175
               <button type="button"
175
               <button type="button"
176
                       class="set-householder-button"
176
                       class="set-householder-button"
177
                       (click)="setAsHouseholder()">
177
                       (click)="setAsHouseholder()">
178
-                世帯主に設定
178
+                主に設定
179
               </button>
179
               </button>
180
             </div>
180
             </div>
181
           </section>
181
           </section>

+ 4
- 5
src/app/pages/kakocho-edit/kakocho-edit.html Просмотреть файл

10
       <div class="page-title-area">
10
       <div class="page-title-area">
11
         <h1>
11
         <h1>
12
           @if (kakocho) {
12
           @if (kakocho) {
13
-            故人編集
13
+            故人編集
14
           } @else {
14
           } @else {
15
-            故人追加
15
+            故人追加
16
           }
16
           }
17
         </h1>
17
         </h1>
18
       </div>
18
       </div>
130
           <button
130
           <button
131
             type="button"
131
             type="button"
132
             class="cancel-button"
132
             class="cancel-button"
133
-            [routerLink]="['/kakocho-list']"
134
-          >
133
+            (click)="cancelKakochoEdit()">
135
             キャンセル
134
             キャンセル
136
           </button>
135
           </button>
137
 
136
 
150
 
149
 
151
     </section>
150
     </section>
152
   </main>
151
   </main>
153
-</div>
152
+</div>

+ 14
- 3
src/app/pages/kakocho-edit/kakocho-edit.scss Просмотреть файл

105
   font-weight: 800;
105
   font-weight: 800;
106
 }
106
 }
107
 
107
 
108
-.form-row input {
108
+.form-row input,
109
+.form-row textarea {
109
   width: 100%;
110
   width: 100%;
110
-  height: 54px;
111
   padding: 0 14px;
111
   padding: 0 14px;
112
   border: 2px solid #d8caba;
112
   border: 2px solid #d8caba;
113
   border-radius: 8px;
113
   border-radius: 8px;
119
   outline: none;
119
   outline: none;
120
 }
120
 }
121
 
121
 
122
-.form-row input:focus {
122
+.form-row input {
123
+  height: 54px;
124
+}
125
+
126
+.form-row textarea {
127
+  min-height: 108px;
128
+  padding-top: 14px;
129
+  resize: vertical;
130
+}
131
+
132
+.form-row input:focus,
133
+.form-row textarea:focus {
123
   border-color: #8a6543;
134
   border-color: #8a6543;
124
   box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.15);
135
   box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.15);
125
 }
136
 }

+ 9
- 14
src/app/pages/kakocho-edit/kakocho-edit.ts Просмотреть файл

9
 import {
9
 import {
10
   ActivatedRoute,
10
   ActivatedRoute,
11
   Router,
11
   Router,
12
-  RouterLink,
13
 } from '@angular/router';
12
 } from '@angular/router';
14
 
13
 
15
 import { AppHeader } from '../../share/header/app-header';
14
 import { AppHeader } from '../../share/header/app-header';
27
     AppHeader,
26
     AppHeader,
28
     AppSideMenu,
27
     AppSideMenu,
29
     ReactiveFormsModule,
28
     ReactiveFormsModule,
30
-    RouterLink,
31
   ],
29
   ],
32
   templateUrl: './kakocho-edit.html',
30
   templateUrl: './kakocho-edit.html',
33
   styleUrl: './kakocho-edit.scss',
31
   styleUrl: './kakocho-edit.scss',
34
 })
32
 })
35
 export class KakochoEdit {
33
 export class KakochoEdit {
36
-
37
   danka?: Danka;
34
   danka?: Danka;
38
   kakocho?: Kakocho;
35
   kakocho?: Kakocho;
39
-
40
   kakochoForm: FormGroup;
36
   kakochoForm: FormGroup;
37
+  dankaId: string;
41
 
38
 
42
   constructor(
39
   constructor(
43
     private fb: FormBuilder,
40
     private fb: FormBuilder,
60
 
57
 
61
     // 檀家ID
58
     // 檀家ID
62
     const dankaId = this.route.snapshot.params['dankaId'];
59
     const dankaId = this.route.snapshot.params['dankaId'];
60
+    this.dankaId = this.route.snapshot.params['dankaId'];
63
 
61
 
64
     if (dankaId) {
62
     if (dankaId) {
65
       this.danka =
63
       this.danka =
137
     this.router.navigate([
135
     this.router.navigate([
138
       '/danka-detail',
136
       '/danka-detail',
139
       this.danka?.id,
137
       this.danka?.id,
140
-    ]);
138
+    ], { queryParams: { tab: 'kakocho' } });
141
   }
139
   }
142
 
140
 
143
   deleteKakocho(): void {
141
   deleteKakocho(): void {
144
-
145
     if (!this.kakocho) {
142
     if (!this.kakocho) {
146
       return;
143
       return;
147
     }
144
     }
148
 
145
 
149
-    this.kakochoService.deleteKakocho(
150
-      this.kakocho.id
151
-    );
146
+    this.kakochoService.deleteKakocho(this.kakocho.id);
152
 
147
 
153
-    this.router.navigate([
154
-      '/danka-detail',
155
-      this.danka?.id,
156
-    ]);
148
+    this.router.navigate(['/danka-detail', this.danka?.id], { queryParams: { tab: 'kakocho' } });
157
   }
149
   }
158
-}
150
+
151
+  cancelKakochoEdit() {
152
+    this.router.navigate(['/danka-detail', this.danka?.id], { queryParams: { tab: 'kakocho' } });  }
153
+}

+ 45
- 23
src/app/pages/memorial-list/memorial-list.html Просмотреть файл

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
-              @for (filter of memorialTypeFilters; track filter.value) {
22
-                <button
23
-                  type="button"
24
-                  class="filter-button"
25
-                  [class.active]="selectedMemorialType === filter.value"
26
-                  (click)="changeMemorialType(filter.value)"
27
-                >
28
-                  {{ filter.label }}
29
-                </button>
30
-              }
27
+            <div class="filter-field">
28
+              <label for="memorialType">回忌</label>
29
+              <select
30
+                id="memorialType"
31
+                [(ngModel)]="selectedMemorialType"
32
+                (ngModelChange)="changeMemorialType($event)"
33
+              >
34
+                @for (filter of memorialTypeFilters; track filter.value) {
35
+                  <option [ngValue]="filter.value">{{ filter.label }}</option>
36
+                }
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
+              />
31
             </div>
48
             </div>
32
           </div>
49
           </div>
33
         </div>
50
         </div>
34
-
35
-        <button type="button" class="reload-button" (click)="createMemorialList()">
36
-          再表示
37
-        </button>
38
       </div>
51
       </div>
39
 
52
 
40
       <div class="list-header-row">
53
       <div class="list-header-row">
41
-        <h2>対象 {{ memorialList.length }} 名</h2>
54
+        <h2>対象 {{ filteredMemorialList.length }} 名</h2>
42
 
55
 
43
         <p>並び順: 没年月日 / 氏名</p>
56
         <p>並び順: 没年月日 / 氏名</p>
44
       </div>
57
       </div>
52
             <div>関係</div>
65
             <div>関係</div>
53
             <div>檀家(世帯)</div>
66
             <div>檀家(世帯)</div>
54
             <div>回忌</div>
67
             <div>回忌</div>
68
+            <div>備考</div>
55
             <div>詳細</div>
69
             <div>詳細</div>
56
           </div>
70
           </div>
57
 
71
 
58
-          @if (memorialList.length > 0) {
59
-            @for (memorial of memorialList; track memorial.id) {
72
+          @if (filteredMemorialList.length > 0) {
73
+            @for (memorial of filteredMemorialList; track memorial.id) {
60
               <div class="memorial-table-row">
74
               <div class="memorial-table-row">
61
                 <div class="person-name">
75
                 <div class="person-name">
62
                   {{ memorial.kaimyo }}
76
                   {{ memorial.kaimyo }}
63
                 </div>
77
                 </div>
64
                 <div>
78
                 <div>
65
-                  {{ memorial.name }}
79
+                  <p class="person-name">{{ memorial.name }}</p>
80
+                  <p class="person-sub">俗名</p>
66
                 </div>
81
                 </div>
67
                 <div>
82
                 <div>
68
                   {{ formatDeathDate(memorial.deathDate) }}
83
                   {{ formatDeathDate(memorial.deathDate) }}
76
                 <div class="memorial-type">
91
                 <div class="memorial-type">
77
                   {{ memorial.memorialType }}
92
                   {{ memorial.memorialType }}
78
                 </div>
93
                 </div>
94
+                <div>
95
+                  {{ memorial.note || '' }}
96
+                </div>
79
                 <div>
97
                 <div>
80
                   <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'}">
81
                     開く
99
                     開く
85
             }
103
             }
86
           } @else {
104
           } @else {
87
             <div class="empty-message">
105
             <div class="empty-message">
88
-              対象となる法要はありません。
106
+              @if (memorialList.length > 0) {
107
+                検索条件に一致する法要はありません。
108
+              } @else {
109
+                対象となる法要はありません。
110
+              }
89
             </div>
111
             </div>
90
           }
112
           }
91
         </div>
113
         </div>

+ 73
- 111
src/app/pages/memorial-list/memorial-list.scss Просмотреть файл

38
 
38
 
39
 .title-filter-area {
39
 .title-filter-area {
40
   display: grid;
40
   display: grid;
41
-  gap: 4px;
41
+  gap: 12px;
42
+  min-width: 0;
42
 }
43
 }
43
 
44
 
44
 .page-title-row h1 {
45
 .page-title-row h1 {
52
 
53
 
53
 .filter-row {
54
 .filter-row {
54
   display: flex;
55
   display: flex;
55
-  align-items: center;
56
-  gap: 28px;
56
+  align-items: flex-end;
57
+  gap: 14px 18px;
57
   flex-wrap: wrap;
58
   flex-wrap: wrap;
59
+  width: 100%;
58
 }
60
 }
59
 
61
 
60
-.year-filter {
61
-  display: flex;
62
-  align-items: center;
63
-  gap: 12px;
62
+.filter-field,
63
+.search-field {
64
+  display: grid;
65
+  gap: 6px;
64
 }
66
 }
65
 
67
 
66
-.year-filter label,
67
-.memorial-filter span {
68
+.filter-field label,
69
+.search-field label {
68
   color: #4b3c31;
70
   color: #4b3c31;
69
-  font-size: 16px;
71
+  font-size: 14px;
70
   font-weight: 800;
72
   font-weight: 800;
71
 }
73
 }
72
 
74
 
73
-.year-filter input {
74
-  width: 96px;
75
-  height: 48px;
75
+.filter-field select,
76
+.search-field input {
77
+  height: 38px;
76
   padding: 0 14px;
78
   padding: 0 14px;
77
   border: 2px solid #d8caba;
79
   border: 2px solid #d8caba;
78
   border-radius: 8px;
80
   border-radius: 8px;
79
   background: #fffdf9;
81
   background: #fffdf9;
80
   color: #2f2720;
82
   color: #2f2720;
81
-  font-size: 17px;
83
+  font-size: 15px;
82
   font-weight: 700;
84
   font-weight: 700;
83
   box-sizing: border-box;
85
   box-sizing: border-box;
86
+  outline: none;
84
 }
87
 }
85
 
88
 
86
-.memorial-filter {
87
-  display: flex;
88
-  align-items: center;
89
-  gap: 8px;
90
-}
91
-
92
-.filter-button {
93
-  width: 108px;
94
-  height: 40px;
95
-  border: 2px solid #d8caba;
96
-  border-radius: 6px;
97
-  background: #f1e7d8;
98
-  color: #2f2720;
99
-  font-size: 15px;
100
-  font-weight: 700;
89
+.filter-field select {
90
+  width: 148px;
101
   cursor: pointer;
91
   cursor: pointer;
102
-  box-sizing: border-box;
103
 }
92
 }
104
 
93
 
105
-.filter-button:hover {
106
-  background: #eadfce;
94
+.search-field {
95
+  flex: 1 1 260px;
96
+  min-width: 260px;
107
 }
97
 }
108
 
98
 
109
-.filter-button.active {
110
-  background: #8a6543;
111
-  border-color: #8a6543;
112
-  color: #ffffff;
99
+.search-field input {
100
+  width: 100%;
113
 }
101
 }
114
 
102
 
115
-.reload-button {
116
-  width: 120px;
117
-  height: 48px;
118
-  margin-top: 30px;
119
-  border: 2px solid #8a6543;
120
-  border-radius: 6px;
121
-  background: #8a6543;
122
-  color: #ffffff;
123
-  font-size: 17px;
124
-  font-weight: 800;
125
-  cursor: pointer;
126
-  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);
127
 }
106
 }
128
 
107
 
129
-.reload-button:hover {
130
-  background: #765639;
108
+.search-field input:focus {
109
+  border-color: #8a6543;
110
+  box-shadow: 0 0 0 3px rgba(138, 101, 67, 0.12);
131
 }
111
 }
132
 
112
 
133
 .list-header-row {
113
 .list-header-row {
156
 
136
 
157
 .memorial-table {
137
 .memorial-table {
158
   display: grid;
138
   display: grid;
159
-  gap: 4px;
139
+  gap: 0;
140
+  border: 2px solid #d8c2aa;
141
+  border-radius: 8px;
142
+  overflow: hidden;
160
 }
143
 }
161
 
144
 
162
 .memorial-table-header,
145
 .memorial-table-header,
163
 .memorial-table-row {
146
 .memorial-table-row {
164
   display: grid;
147
   display: grid;
165
-  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;
166
   align-items: center;
149
   align-items: center;
167
-  column-gap: 12px;
150
+  gap: 12px;
168
 }
151
 }
169
 
152
 
170
 .memorial-table-header {
153
 .memorial-table-header {
171
-  min-height: 38px;
172
-  padding: 0 12px;
173
-  border: 2px solid #d8caba;
174
-  border-radius: 8px;
175
-  background: #eadfce;
176
-  color: #5a4a3c;
177
-  font-size: 15px;
154
+  min-height: 46px;
155
+  padding: 0 14px;
156
+  background: #efe4d6;
157
+  color: #111111;
158
+  font-size: 14px;
178
   font-weight: 800;
159
   font-weight: 800;
179
   box-sizing: border-box;
160
   box-sizing: border-box;
180
 }
161
 }
181
 
162
 
182
 .memorial-table-row {
163
 .memorial-table-row {
183
-  min-height: 58px;
184
-  padding: 0 12px;
185
-  border: 2px solid #d8caba;
186
-  border-radius: 10px;
164
+  min-height: 78px;
165
+  padding: 10px 14px;
166
+  border-top: 1px solid #d8c2aa;
187
   background: #fffdf9;
167
   background: #fffdf9;
188
-  color: #2f2720;
189
-  font-size: 16px;
168
+  color: #111111;
169
+  font-size: 15px;
190
   box-sizing: border-box;
170
   box-sizing: border-box;
191
 }
171
 }
192
 
172
 
193
 .memorial-table-row:hover {
173
 .memorial-table-row:hover {
194
-  background: #f6efe6;
174
+  background: #fff8ee;
195
 }
175
 }
196
 
176
 
197
 .person-name {
177
 .person-name {
178
+  margin: 0;
198
   font-weight: 800;
179
   font-weight: 800;
199
 }
180
 }
200
 
181
 
182
+.person-sub {
183
+  margin: 4px 0 0;
184
+  color: #111111;
185
+  font-size: 13px;
186
+  line-height: 1.35;
187
+}
188
+
201
 .memorial-type {
189
 .memorial-type {
202
-  color: #8a6543;
190
+  color: #111111;
203
   font-weight: 800;
191
   font-weight: 800;
204
 }
192
 }
205
 
193
 
206
 .detail-link {
194
 .detail-link {
207
-  color: #3f6f45;
208
-  font-size: 15px;
195
+  width: 64px;
196
+  height: 34px;
197
+  border: 2px solid #8a6543;
198
+  border-radius: 6px;
199
+  background: #ffffff;
200
+  color: #8a6543;
201
+  font-size: 14px;
209
   font-weight: 800;
202
   font-weight: 800;
210
   text-decoration: none;
203
   text-decoration: none;
204
+  display: flex;
205
+  align-items: center;
206
+  justify-content: center;
207
+  box-sizing: border-box;
211
 }
208
 }
212
 
209
 
213
 .detail-link:hover {
210
 .detail-link:hover {
214
-  text-decoration: underline;
211
+  background: #f6efe6;
212
+  text-decoration: none;
215
 }
213
 }
216
 
214
 
217
 .empty-message {
215
 .empty-message {
218
   min-height: 58px;
216
   min-height: 58px;
219
   padding: 18px 20px;
217
   padding: 18px 20px;
220
-  border: 2px solid #d8caba;
221
-  border-radius: 10px;
222
   background: #fffdf9;
218
   background: #fffdf9;
223
   color: #7b6b5c;
219
   color: #7b6b5c;
224
   font-size: 15px;
220
   font-size: 15px;
226
   box-sizing: border-box;
222
   box-sizing: border-box;
227
 }
223
 }
228
 
224
 
229
-.notice-box {
230
-  margin-top: 26px;
231
-  padding: 18px 26px;
232
-  border: 2px solid #d8caba;
233
-  border-radius: 10px;
234
-  background: #fffdf9;
235
-  box-sizing: border-box;
236
-}
237
-
238
-.notice-box p {
239
-  margin: 0;
240
-  color: #7b6b5c;
241
-  font-size: 15px;
242
-  line-height: 1.7;
243
-}
244
-
245
-.bottom-note {
246
-  width: 700px;
247
-  margin: 18px 0 22px 36px;
248
-  padding: 4px 12px;
249
-  border: 2px solid #d8caba;
250
-  border-radius: 4px;
251
-  background: #eadfce;
252
-  color: #7b6b5c;
253
-  font-size: 14px;
254
-  box-sizing: border-box;
255
-}
256
-
257
 @media (max-width: 1100px) {
225
 @media (max-width: 1100px) {
258
   .page-title-row {
226
   .page-title-row {
259
     flex-direction: column;
227
     flex-direction: column;
260
   }
228
   }
261
 
229
 
262
-  .reload-button {
263
-    margin-top: 0;
264
-  }
265
-
266
   .memorial-table {
230
   .memorial-table {
267
     overflow-x: auto;
231
     overflow-x: auto;
268
   }
232
   }
298
     gap: 14px;
262
     gap: 14px;
299
   }
263
   }
300
 
264
 
301
-  .memorial-filter {
302
-    flex-wrap: wrap;
265
+  .filter-field,
266
+  .filter-field select,
267
+  .search-field {
268
+    width: 100%;
303
   }
269
   }
304
 
270
 
305
   .list-header-row {
271
   .list-header-row {
308
     gap: 8px;
274
     gap: 8px;
309
   }
275
   }
310
 
276
 
311
-  .bottom-note {
312
-    width: auto;
313
-    margin: 10px 20px 22px;
314
-  }
315
 }
277
 }

+ 51
- 0
src/app/pages/memorial-list/memorial-list.ts Просмотреть файл

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: '一周忌' },
25
     { label: '十三回忌', value: '十三回忌' },
32
     { label: '十三回忌', value: '十三回忌' },
26
     { label: '十七回忌', value: '十七回忌' },
33
     { label: '十七回忌', value: '十七回忌' },
27
     { label: '二十三回忌', value: '二十三回忌' },
34
     { label: '二十三回忌', value: '二十三回忌' },
35
+    { label: '二十五回忌', value: '二十五回忌' },
36
+    { label: '二十七回忌', value: '二十七回忌' },
37
+    { label: '三十三回忌', value: '三十三回忌' },
38
+    { label: '三十七回忌', value: '三十七回忌' },
39
+    { label: '四十三回忌', value: '四十三回忌' },
40
+    { label: '四十七回忌', value: '四十七回忌' },
41
+    { label: '五十回忌', value: '五十回忌' },
42
+    { label: '百回忌', value: '百回忌' },
28
   ];
43
   ];
29
 
44
 
30
   constructor(
45
   constructor(
57
         householdName: danka?.householdName ?? '不明',
72
         householdName: danka?.householdName ?? '不明',
58
         deathDate: kakocho.deathDate,
73
         deathDate: kakocho.deathDate,
59
         memorialType: memorialType,
74
         memorialType: memorialType,
75
+        note: kakocho.note,
60
       };
76
       };
61
       this.memorialList.push(memorialTarget);
77
       this.memorialList.push(memorialTarget);
62
     });
78
     });
75
     this.createMemorialList();
91
     this.createMemorialList();
76
   }
92
   }
77
 
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
+
78
   formatDeathDate(deathDate: string): string {
113
   formatDeathDate(deathDate: string): string {
79
     const [, month, day] = deathDate.split('-').map(Number);
114
     const [, month, day] = deathDate.split('-').map(Number);
80
     if (!month || !day) {
115
     if (!month || !day) {
98
         return '十七回忌';
133
         return '十七回忌';
99
       case 22:
134
       case 22:
100
         return '二十三回忌';
135
         return '二十三回忌';
136
+      case 24:
137
+        return '二十五回忌';
138
+      case 26:
139
+        return '二十七回忌';
140
+      case 32:
141
+        return '三十三回忌';
142
+      case 36:
143
+        return '三十七回忌';
144
+      case 42:
145
+        return '四十三回忌';
146
+      case 46:
147
+        return '四十七回忌';
148
+      case 49:
149
+        return '五十回忌';
150
+      case 99:
151
+        return '百回忌';
101
       default:
152
       default:
102
         return '';
153
         return '';
103
     }
154
     }

+ 1
- 1
src/app/pages/search/search.html Просмотреть файл

53
                     {{ danka.householdName }}
53
                     {{ danka.householdName }}
54
                   </div>
54
                   </div>
55
                   <div>
55
                   <div>
56
-                    世帯主: {{ danka.householder }}
56
+                    施主名: {{ danka.householder }}
57
                   </div>
57
                   </div>
58
                   <div>
58
                   <div>
59
                     住所: {{ danka.address }}
59
                     住所: {{ danka.address }}

+ 8
- 2
src/app/services/dankaService.ts Просмотреть файл

9
     {
9
     {
10
       id: '1',
10
       id: '1',
11
       householdName: '鈴木家',
11
       householdName: '鈴木家',
12
+      householdFurigana: 'すずきけ',
12
       householder: '鈴木 太郎',
13
       householder: '鈴木 太郎',
14
+      householderFurigana: 'すずき たろう',
13
       postalCode: '123-4567',
15
       postalCode: '123-4567',
14
       address: '市内 1-2-3',
16
       address: '市内 1-2-3',
17
+      note: '寺報送付あり。年忌法要の案内は施主へ連絡。',
15
       updatedAt: '2026-05-28',
18
       updatedAt: '2026-05-28',
16
       phones: [
19
       phones: [
17
         {
20
         {
20
         },
23
         },
21
         {
24
         {
22
           tel: '090-1234-5678',
25
           tel: '090-1234-5678',
23
-          note: '世帯主',
26
+          note: '主',
24
         },
27
         },
25
       ],
28
       ],
26
     },
29
     },
27
     {
30
     {
28
       id: '2',
31
       id: '2',
29
       householdName: '古田家',
32
       householdName: '古田家',
33
+      householdFurigana: 'ふるたけ',
30
       householder: '古田 太郎',
34
       householder: '古田 太郎',
35
+      householderFurigana: 'ふるた たろう',
31
       postalCode: '234-4567',
36
       postalCode: '234-4567',
32
       address: '市内 1-2-3',
37
       address: '市内 1-2-3',
38
+      note: '電話連絡を優先。',
33
       updatedAt: '2026-05-28',
39
       updatedAt: '2026-05-28',
34
       phones: [
40
       phones: [
35
         {
41
         {
38
         },
44
         },
39
         {
45
         {
40
           tel: '080-7890-4567',
46
           tel: '080-7890-4567',
41
-          note: '世帯主',
47
+          note: '主',
42
         },
48
         },
43
       ],
49
       ],
44
     }
50
     }

+ 1
- 1
src/app/services/family-service.ts Просмотреть файл

13
       name: '鈴木 花子',
13
       name: '鈴木 花子',
14
       relationship: '母',
14
       relationship: '母',
15
       birthDate: '1975-01-01',
15
       birthDate: '1975-01-01',
16
-      note: '次の世帯主',
16
+      note: '次の主',
17
       fatherId: '5',
17
       fatherId: '5',
18
       motherId: '6',
18
       motherId: '6',
19
       spouseId: '3',
19
       spouseId: '3',

Загрузка…
Отмена
Сохранить