poohr 3 viikkoa sitten
vanhempi
commit
44f033cb93

+ 63
- 30
src/app/pages/danka-detail/danka-detail.scss Näytä tiedosto

@@ -2,29 +2,29 @@
2 2
   position: relative;
3 3
   display: block;
4 4
   min-height: 100vh;
5
-  background: #f4eee4;
5
+  background: #f6f0e7;
6 6
   color: #2f2720;
7 7
 }
8 8
 
9 9
 .danka-detail-page {
10
-  display: flex;
11
-  align-items: flex-start;
12
-  gap: 8px;
13
-  background: #f4eee4;
10
+  display: grid;
11
+  grid-template-columns: 172px minmax(0, 1fr);
12
+  gap: 20px;
13
+  padding: 0 38px 36px 0;
14
+  background: #f6f0e7;
14 15
 }
15 16
 
16 17
 .danka-detail-main {
17
-  flex: 1;
18
-  padding-right: 34px;
18
+  min-width: 0;
19 19
   box-sizing: border-box;
20 20
 }
21 21
 
22 22
 .detail-panel {
23
-  min-height: 650px;
24
-  padding: 26px 34px 36px;
25
-  background: #ffffff;
23
+  min-height: 760px;
24
+  padding: 34px 42px 40px;
25
+  background: #fffdf9;
26 26
   border: 2px solid #d8caba;
27
-  border-radius: 76px;
27
+  border-radius: 64px;
28 28
   box-sizing: border-box;
29 29
 }
30 30
 
@@ -32,16 +32,17 @@
32 32
   display: flex;
33 33
   justify-content: space-between;
34 34
   align-items: flex-start;
35
-  margin-bottom: 20px;
35
+  gap: 24px;
36
+  margin-bottom: 22px;
36 37
 }
37 38
 
38 39
 .page-title-row h1 {
39
-  margin: 0 0 8px;
40
+  margin: 0 0 18px;
40 41
   color: #2f2720;
41
-  font-size: 32px;
42
-  line-height: 1.2;
42
+  font-size: 34px;
43
+  line-height: 1.1;
43 44
   font-weight: 800;
44
-  letter-spacing: 0.02em;
45
+  letter-spacing: 0;
45 46
 }
46 47
 
47 48
 .tab-list {
@@ -83,7 +84,7 @@
83 84
 .edit-button {
84 85
   width: 140px;
85 86
   height: 46px;
86
-  margin-top: 36px;
87
+  margin-top: 54px;
87 88
   border: 2px solid #8a6543;
88 89
   border-radius: 6px;
89 90
   background: #ffffff;
@@ -104,7 +105,7 @@
104 105
   padding: 14px 24px;
105 106
   border: 2px solid #d8caba;
106 107
   border-radius: 8px;
107
-  background: #f1e7d8;
108
+  background: #fbf7f0;
108 109
   display: flex;
109 110
   align-items: center;
110 111
   box-sizing: border-box;
@@ -169,7 +170,7 @@
169 170
 .add-button {
170 171
   width: 140px;
171 172
   height: 46px;
172
-  margin-top: 36px;
173
+  margin-top: 54px;
173 174
   border: 2px solid #8a6543;
174 175
   border-radius: 6px;
175 176
   background: #ffffff;
@@ -187,7 +188,7 @@
187 188
 .family-page-add-button {
188 189
   width: 140px;
189 190
   height: 46px;
190
-  margin-top: 36px;
191
+  margin-top: 54px;
191 192
   border: 2px solid #8a6543;
192 193
   border-radius: 6px;
193 194
   background: #ffffff;
@@ -209,12 +210,12 @@
209 210
 .detail-content {
210 211
   display: grid;
211 212
   grid-template-columns: minmax(0, 1fr) 420px;
212
-  gap: 18px;
213
+  gap: 20px;
213 214
   align-items: start;
214 215
 }
215 216
 
216 217
 .basic-info-section {
217
-  padding: 24px 20px 18px;
218
+  padding: 24px 20px 22px;
218 219
   border: 2px solid #d8caba;
219 220
   border-radius: 12px;
220 221
   background: #fffdf9;
@@ -222,7 +223,7 @@
222 223
 }
223 224
 
224 225
 .section-heading {
225
-  margin-bottom: 8px;
226
+  margin-bottom: 14px;
226 227
 }
227 228
 
228 229
 .section-heading h2 {
@@ -230,6 +231,7 @@
230 231
   color: #2f2720;
231 232
   font-size: 22px;
232 233
   font-weight: 800;
234
+  line-height: 1.3;
233 235
 }
234 236
 
235 237
 .section-heading p {
@@ -276,7 +278,7 @@
276 278
   border-radius: 6px;
277 279
   background: #f3eee8;
278 280
   color: #4b3c31;
279
-  font-size: 17px;
281
+  font-size: 16px;
280 282
   font-weight: 800;
281 283
   display: flex;
282 284
   align-items: center;
@@ -290,7 +292,8 @@
290 292
   border-radius: 0;
291 293
   background: transparent;
292 294
   color: #2f2720;
293
-  font-size: 17px;
295
+  font-size: 16px;
296
+  font-weight: 700;
294 297
   box-sizing: border-box;
295 298
   display: flex;
296 299
   align-items: center;
@@ -390,6 +393,7 @@
390 393
   color: #2f2720;
391 394
   font-size: 22px;
392 395
   font-weight: 800;
396
+  line-height: 1.3;
393 397
   display: flex;
394 398
   align-items: center;
395 399
   gap: 12px;
@@ -452,7 +456,7 @@
452 456
 .next-memorial h3 {
453 457
   margin: 0 0 16px;
454 458
   color: #4b3c31;
455
-  font-size: 17px;
459
+  font-size: 18px;
456 460
   font-weight: 800;
457 461
   display: flex;
458 462
   align-items: center;
@@ -535,8 +539,9 @@
535 539
 
536 540
   h2 {
537 541
     margin: 0;
538
-    font-size: 20px;
539
-    font-weight: 700;
542
+    font-size: 22px;
543
+    line-height: 1.3;
544
+    font-weight: 800;
540 545
   }
541 546
 }
542 547
 
@@ -808,8 +813,9 @@
808 813
 
809 814
   h2 {
810 815
     margin: 0;
811
-    font-size: 20px;
812
-    font-weight: 700;
816
+    font-size: 22px;
817
+    line-height: 1.3;
818
+    font-weight: 800;
813 819
   }
814 820
 }
815 821
 
@@ -1273,6 +1279,33 @@
1273 1279
   color: #ffffff;
1274 1280
 }
1275 1281
 
1282
+@media (max-width: 1100px) {
1283
+  .danka-detail-page {
1284
+    grid-template-columns: 1fr;
1285
+    padding: 0 24px 32px;
1286
+  }
1287
+
1288
+  .detail-panel {
1289
+    min-height: auto;
1290
+    border-radius: 28px;
1291
+    padding: 28px 24px 32px;
1292
+  }
1293
+
1294
+  .detail-content {
1295
+    grid-template-columns: 1fr;
1296
+  }
1297
+
1298
+  .page-title-row {
1299
+    flex-direction: column;
1300
+  }
1301
+
1302
+  .edit-button,
1303
+  .add-button,
1304
+  .family-page-add-button {
1305
+    margin-top: 0;
1306
+  }
1307
+}
1308
+
1276 1309
 /* 家族表が狭い画面では横スクロール */
1277 1310
 @media (max-width: 800px) {
1278 1311
   .detail-content,

+ 1
- 0
src/app/pages/danka-detail/danka-detail.ts Näytä tiedosto

@@ -106,6 +106,7 @@ export class DankaDetail implements AfterViewInit {
106 106
     const id = this.route.snapshot.params['id'];
107 107
     if (id) {
108 108
       this.danka = this.dankaService.getDankaById(id);
109
+      this.dankaService.recordDankaOpened(id);
109 110
       this.marriageRelations = this.marriageRelationService.getMarriageRelationsByDankaId(id);
110 111
       this.families = this.sortFamiliesByHouseholder(this.familyService.getFamiliesByDankaId(id));
111 112
       this.selectedFamily = this.families[0];

+ 21
- 17
src/app/pages/danka-edit/danka-edit.html Näytä tiedosto

@@ -100,7 +100,6 @@
100 100
           <section class="phone-edit-section">
101 101
             <div class="section-heading">
102 102
               <h2>電話番号(複数登録)</h2>
103
-              <p>番号と備考を複数登録できます。</p>
104 103
             </div>
105 104
 
106 105
             <div formArrayName="phones" class="phone-table">
@@ -148,22 +147,27 @@
148 147
         </div>
149 148
 
150 149
         <div class="bottom-actions">
151
-          <button type="button" class="delete-button" (click)="deleteDanka()">
152
-            削除
153
-          </button>
154
-
155
-          <button type="button" class="cancel-button" [routerLink]="['/danka-detail', danka?.id]">
156
-            キャンセル
157
-          </button>
158
-
159
-          <button
160
-            type="button"
161
-            class="save-button"
162
-            [disabled]="dankaForm.invalid"
163
-            (click)="saveDanka()"
164
-          >
165
-            保存
166
-          </button>
150
+          <div class="left-actions"></div>
151
+
152
+          <div class="right-actions">
153
+            @if (danka) {
154
+              <button type="button" class="delete-button" (click)="deleteDanka()">
155
+                削除
156
+              </button>
157
+            }
158
+            <button type="button" class="cancel-button" [routerLink]="danka ? ['/danka-detail', danka.id] : ['/danka-list']">
159
+              キャンセル
160
+            </button>
161
+
162
+            <button
163
+              type="button"
164
+              class="save-button"
165
+              [disabled]="dankaForm.invalid"
166
+              (click)="saveDanka()"
167
+            >
168
+              保存
169
+            </button>
170
+          </div>
167 171
         </div>
168 172
       </form>
169 173
     </section>

+ 33
- 16
src/app/pages/danka-edit/danka-edit.scss Näytä tiedosto

@@ -2,7 +2,7 @@
2 2
   position: relative;
3 3
   display: block;
4 4
   min-height: 100vh;
5
-  background: #f4eee4;
5
+  background: #f6f0e7;
6 6
   color: #2f2720;
7 7
 }
8 8
 
@@ -11,7 +11,7 @@
11 11
   grid-template-columns: 172px minmax(0, 1fr);
12 12
   gap: 20px;
13 13
   padding: 0 38px 36px 0;
14
-  background: #f4eee4;
14
+  background: #f6f0e7;
15 15
 }
16 16
 
17 17
 .danka-edit-main {
@@ -22,7 +22,7 @@
22 22
 .edit-panel {
23 23
   min-height: 760px;
24 24
   padding: 34px 42px 40px;
25
-  background: #ffffff;
25
+  background: #fffdf9;
26 26
   border: 2px solid #d8caba;
27 27
   border-radius: 64px;
28 28
   box-sizing: border-box;
@@ -55,20 +55,24 @@
55 55
 
56 56
 .edit-content {
57 57
   display: grid;
58
-  grid-template-columns: minmax(0, 1fr) 500px;
59
-  gap: 32px;
58
+  grid-template-columns: minmax(0, 1fr) 430px;
59
+  gap: 22px;
60 60
   align-items: start;
61 61
 }
62 62
 
63 63
 .basic-edit-section,
64 64
 .phone-edit-section {
65
-  padding-top: 0;
65
+  padding: 26px 28px 28px;
66
+  border: 2px solid #d8caba;
67
+  border-radius: 12px;
68
+  background: #fffdf9;
69
+  box-sizing: border-box;
66 70
 }
67 71
 
68 72
 .basic-edit-section h2,
69 73
 .phone-edit-section h2,
70 74
 .support-box h2 {
71
-  margin: 0;
75
+  margin: 0 0 18px;
72 76
   color: #2f2720;
73 77
   font-size: 22px;
74 78
   line-height: 1.3;
@@ -83,7 +87,9 @@
83 87
 
84 88
 /* 基本情報 */
85 89
 .form-list {
86
-  margin-top: 12px;
90
+  margin-top: 0;
91
+  padding-top: 18px;
92
+  border-top: 2px solid #eadfce;
87 93
 }
88 94
 
89 95
 .form-field {
@@ -94,10 +100,10 @@
94 100
 
95 101
 .form-row {
96 102
   display: grid;
97
-  grid-template-columns: 140px 1fr;
103
+  grid-template-columns: 132px 1fr;
98 104
   align-items: center;
99
-  gap: 14px;
100
-  margin-bottom: 12px;
105
+  gap: 18px;
106
+  margin-bottom: 14px;
101 107
 }
102 108
 
103 109
 .form-row label {
@@ -124,8 +130,12 @@
124 130
   padding: 0 14px;
125 131
 }
126 132
 
133
+.form-row:has(#postalCode) input {
134
+  max-width: 300px;
135
+}
136
+
127 137
 .form-row textarea {
128
-  min-height: 104px;
138
+  min-height: 150px;
129 139
   padding: 12px 14px;
130 140
   line-height: 1.6;
131 141
   resize: vertical;
@@ -157,7 +167,7 @@
157 167
 .phone-table-header,
158 168
 .phone-table-row {
159 169
   display: grid;
160
-  grid-template-columns: 1.35fr 1.45fr 84px;
170
+  grid-template-columns: minmax(110px, 1fr) minmax(88px, 0.8fr) 72px;
161 171
   align-items: center;
162 172
   gap: 10px;
163 173
 }
@@ -235,7 +245,7 @@
235 245
 .phone-action {
236 246
   display: flex;
237 247
   justify-content: flex-end;
238
-  margin-top: 10px;
248
+  margin-top: 16px;
239 249
 }
240 250
 
241 251
 .add-phone-button {
@@ -277,10 +287,17 @@
277 287
 /* 下部ボタン */
278 288
 .bottom-actions {
279 289
   display: flex;
280
-  justify-content: flex-end;
290
+  justify-content: space-between;
291
+  align-items: center;
292
+  gap: 12px;
293
+  margin-top: 36px;
294
+}
295
+
296
+.left-actions,
297
+.right-actions {
298
+  display: flex;
281 299
   align-items: center;
282 300
   gap: 12px;
283
-  margin-top: 26px;
284 301
 }
285 302
 
286 303
 .delete-button,

+ 19
- 25
src/app/pages/dashboard/dashboard.html Näytä tiedosto

@@ -46,7 +46,6 @@
46 46
           <div>
47 47
             <h2>最近開いた檀家・世帯</h2>
48 48
           </div>
49
-          <a class="text-link" href="#">一覧へ</a>
50 49
         </div>
51 50
 
52 51
         <div class="recent-table" role="table" aria-label="最近開いた檀家">
@@ -58,29 +57,25 @@
58 57
             <div class="cell" role="columnheader">最終更新</div>
59 58
           </div>
60 59
 
61
-          <a class="recent-row" href="#" role="row">
62
-            <div class="cell" role="cell">鈴木 太郎</div>
63
-            <div class="cell muted" role="cell">すずき</div>
64
-            <div class="cell" role="cell">市内 1-2-3</div>
65
-            <div class="cell" role="cell">6月12日</div>
66
-            <div class="cell muted" role="cell">今日</div>
67
-          </a>
68
-
69
-          <a class="recent-row" href="#" role="row">
70
-            <div class="cell" role="cell">佐藤 恵一</div>
71
-            <div class="cell muted" role="cell">さとう</div>
72
-            <div class="cell" role="cell">市内 2-8-1</div>
73
-            <div class="cell" role="cell">7月4日</div>
74
-            <div class="cell muted" role="cell">昨日</div>
75
-          </a>
76
-
77
-          <a class="recent-row" href="#" role="row">
78
-            <div class="cell" role="cell">田中 雪子</div>
79
-            <div class="cell muted" role="cell">たなか</div>
80
-            <div class="cell" role="cell">市内 3-4-6</div>
81
-            <div class="cell muted" role="cell">未設定</div>
82
-            <div class="cell muted" role="cell">5日前</div>
83
-          </a>
60
+          @if (recentDankaList.length > 0) {
61
+            @for (recent of recentDankaList; track recent.danka.id) {
62
+              <a class="recent-row" [routerLink]="['/danka-detail', recent.danka.id]" role="row">
63
+                <div class="cell" role="cell">{{ recent.danka.householder }}</div>
64
+                <div class="cell muted" role="cell">{{ recent.danka.householderFurigana }}</div>
65
+                <div class="cell" role="cell">{{ recent.danka.address }}</div>
66
+                <div class="cell" role="cell">{{ recent.nextMemorialLabel }}</div>
67
+                <div class="cell muted" role="cell">{{ recent.updatedAtLabel }}</div>
68
+              </a>
69
+            }
70
+          } @else {
71
+            <div class="recent-row" role="row">
72
+              <div class="cell muted" role="cell">表示できる檀家・世帯はありません</div>
73
+              <div class="cell muted" role="cell">-</div>
74
+              <div class="cell muted" role="cell">-</div>
75
+              <div class="cell muted" role="cell">-</div>
76
+              <div class="cell muted" role="cell">-</div>
77
+            </div>
78
+          }
84 79
         </div>
85 80
       </section>
86 81
 
@@ -89,7 +84,6 @@
89 84
           <div>
90 85
             <h2>近日の法要・命日</h2>
91 86
           </div>
92
-          <a class="text-link" href="#">年次法要一覧へ</a>
93 87
         </div>
94 88
 
95 89
         <div class="upcoming-list">

+ 65
- 0
src/app/pages/dashboard/dashboard.ts Näytä tiedosto

@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
2 2
 import { RouterLink } from '@angular/router';
3 3
 import { KakochoService } from '../../services/kakocho-service';
4 4
 import { DankaService } from '../../services/dankaService';
5
+import { Danka } from '../../models/danka';
5 6
 import { AppHeader } from '../../share/header/app-header';
6 7
 import { AppSideMenu } from '../../share/side-menu/app-side-menu';
7 8
 
@@ -16,6 +17,12 @@ interface UpcomingMemorial {
16 17
   status: '準備確認' | '要確認';
17 18
 }
18 19
 
20
+interface RecentDanka {
21
+  danka: Danka;
22
+  nextMemorialLabel: string;
23
+  updatedAtLabel: string;
24
+}
25
+
19 26
 @Component({
20 27
   selector: 'app-dashboard',
21 28
   imports: [AppHeader, AppSideMenu, RouterLink],
@@ -27,6 +34,7 @@ export class Dashboard {
27 34
   todayMemorialCount = 0;
28 35
   upcomingWeeklyMemorialCount = 0;
29 36
   monthlyMemorialCount = 0;
37
+  recentDankaList: RecentDanka[] = [];
30 38
   upcomingMemorials: UpcomingMemorial[] = [];
31 39
 
32 40
   private readonly targetYear = new Date().getFullYear();
@@ -37,9 +45,18 @@ export class Dashboard {
37 45
   ) {
38 46
     this.setWeeklyMemorialSummary();
39 47
     this.setMonthlyMemorialSummary();
48
+    this.setRecentDankaList();
40 49
     this.setUpcomingMemorials();
41 50
   }
42 51
 
52
+  private setRecentDankaList(): void {
53
+    this.recentDankaList = this.dankaService.getRecentDankaList(5).map((danka) => ({
54
+      danka,
55
+      nextMemorialLabel: this.getNextMemorialLabel(danka.id),
56
+      updatedAtLabel: this.formatUpdatedAt(danka.updatedAt),
57
+    }));
58
+  }
59
+
43 60
   private setWeeklyMemorialSummary(): void {
44 61
     const today = this.toDateOnly(new Date());
45 62
     const weekEnd = this.addDays(this.getWeekStart(today), 6);
@@ -150,6 +167,54 @@ export class Dashboard {
150 167
     return `${date.getMonth() + 1}月${date.getDate()}日`;
151 168
   }
152 169
 
170
+  private getNextMemorialLabel(dankaId: string): string {
171
+    const today = this.toDateOnly(new Date());
172
+    const nextMemorial = this.kakochoService
173
+      .getKakochoByDankaId(dankaId)
174
+      .map((kakocho) => {
175
+        const deathDate = this.parseDate(kakocho.deathDate);
176
+        if (!deathDate) {
177
+          return null;
178
+        }
179
+
180
+        const memorialDate = new Date(this.targetYear, deathDate.getMonth(), deathDate.getDate());
181
+        if (memorialDate < today || !this.getMemorialType(deathDate)) {
182
+          return null;
183
+        }
184
+
185
+        return memorialDate;
186
+      })
187
+      .filter((date): date is Date => date !== null)
188
+      .sort((a, b) => a.getTime() - b.getTime())[0];
189
+
190
+    return nextMemorial ? this.formatDateLabel(nextMemorial, today) : '未設定';
191
+  }
192
+
193
+  private formatUpdatedAt(updatedAt: string): string {
194
+    const updatedDate = this.parseDate(updatedAt);
195
+    const today = this.toDateOnly(new Date());
196
+
197
+    if (!updatedDate) {
198
+      return '未登録';
199
+    }
200
+
201
+    const diffDays = Math.floor((today.getTime() - updatedDate.getTime()) / 86400000);
202
+
203
+    if (diffDays === 0) {
204
+      return '今日';
205
+    }
206
+
207
+    if (diffDays === 1) {
208
+      return '昨日';
209
+    }
210
+
211
+    if (diffDays > 1 && diffDays <= 7) {
212
+      return `${diffDays}日前`;
213
+    }
214
+
215
+    return `${updatedDate.getMonth() + 1}月${updatedDate.getDate()}日`;
216
+  }
217
+
153 218
   private getWeekStart(date: Date): Date {
154 219
     const day = date.getDay();
155 220
     const diff = day === 0 ? -6 : 1 - day;

+ 59
- 0
src/app/services/dankaService.ts Näytä tiedosto

@@ -5,6 +5,8 @@ import { Danka } from '../models/danka';
5 5
   providedIn: 'root',
6 6
 })
7 7
 export class DankaService {
8
+  private readonly recentDankaStorageKey = 'kaimyo-management.recent-danka';
9
+
8 10
   private dankaList: Danka[] = [
9 11
     {
10 12
       id: '1',
@@ -60,6 +62,33 @@ export class DankaService {
60 62
     return this.dankaList.find((danka) => danka.id === id);
61 63
   }
62 64
 
65
+  recordDankaOpened(id: string): void {
66
+    if (!this.getDankaById(id)) {
67
+      return;
68
+    }
69
+
70
+    const recentIds = [
71
+      id,
72
+      ...this.getRecentDankaIds().filter((recentId) => recentId !== id),
73
+    ].slice(0, 5);
74
+
75
+    this.saveRecentDankaIds(recentIds);
76
+  }
77
+
78
+  getRecentDankaList(limit = 5): Danka[] {
79
+    const recentDanka = this.getRecentDankaIds()
80
+      .map((id) => this.getDankaById(id))
81
+      .filter((danka): danka is Danka => danka !== undefined);
82
+
83
+    if (recentDanka.length > 0) {
84
+      return recentDanka.slice(0, limit);
85
+    }
86
+
87
+    return [...this.dankaList]
88
+      .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
89
+      .slice(0, limit);
90
+  }
91
+
63 92
   //DBへの檀家情報の登録
64 93
   saveDanka(updatedDanka: Danka): void {
65 94
     const index = this.dankaList.findIndex((danka) => danka.id === updatedDanka.id);
@@ -78,4 +107,34 @@ export class DankaService {
78 107
     }
79 108
     this.dankaList.splice(index, 1);
80 109
   }
110
+
111
+  private getRecentDankaIds(): string[] {
112
+    if (!this.canUseLocalStorage()) {
113
+      return [];
114
+    }
115
+
116
+    const value = localStorage.getItem(this.recentDankaStorageKey);
117
+    if (!value) {
118
+      return [];
119
+    }
120
+
121
+    try {
122
+      const parsed = JSON.parse(value);
123
+      return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string') : [];
124
+    } catch {
125
+      return [];
126
+    }
127
+  }
128
+
129
+  private saveRecentDankaIds(ids: string[]): void {
130
+    if (!this.canUseLocalStorage()) {
131
+      return;
132
+    }
133
+
134
+    localStorage.setItem(this.recentDankaStorageKey, JSON.stringify(ids));
135
+  }
136
+
137
+  private canUseLocalStorage(): boolean {
138
+    return typeof localStorage !== 'undefined';
139
+  }
81 140
 }

Loading…
Peruuta
Tallenna