설명 없음
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

danka-detail.html 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. <app-header></app-header>
  2. <div class="danka-detail-page">
  3. <app-side-menu></app-side-menu>
  4. <main class="danka-detail-main">
  5. <section class="detail-panel">
  6. @if (danka) {
  7. <div class="page-title-row">
  8. <div>
  9. <h1>
  10. 檀家詳細 - {{ danka.householdName }}
  11. </h1>
  12. <nav class="tab-list">
  13. <button type="button" class="tab-button" [class.active]="selectedTab === 'basic'"
  14. (click)="selectedTab = 'basic'">
  15. 基本情報
  16. </button>
  17. <button type="button" class="tab-button" [class.active]="selectedTab === 'family'"
  18. (click)="selectedTab = 'family'">
  19. 家族
  20. </button>
  21. <button type="button" class="tab-button" [class.active]="selectedTab === 'kakocho'"
  22. (click)="selectedTab = 'kakocho'">
  23. 過去帳
  24. </button>
  25. <button type="button" class="tab-button" [class.active]="selectedTab === 'familyTree'"
  26. (click)="openFamilyTreeTab()">
  27. 家系図
  28. </button>
  29. </nav>
  30. </div>
  31. @if (selectedTab === 'basic') {
  32. <button type="button" class="edit-button" [routerLink]="['/danka-edit', danka.id]">
  33. 編集
  34. </button>
  35. }
  36. @if (selectedTab === 'kakocho') {
  37. <button type="button" class="add-button" [routerLink]="['/kakocho-edit', danka.id]">
  38. 故人を登録
  39. </button>
  40. }
  41. </div>
  42. @if (selectedTab === 'basic' || selectedTab === 'kakocho') {
  43. <section class="family-summary">
  44. @if (selectedTab === 'basic') {
  45. <div class="family-name-area">
  46. <p class="family-name">{{ danka.householdName }}</p>
  47. <p class="family-head">更新日: {{ formatUpdatedAt(danka.updatedAt) }}</p>
  48. </div>
  49. }
  50. @if (selectedTab === 'kakocho') {
  51. <div class="family-name-area">
  52. <p class="family-name">{{ danka.householdName }}の過去帳{{ kakocholist.length }} 名</p>
  53. </div>
  54. }
  55. </section>
  56. }
  57. @if (selectedTab === 'basic') {
  58. <div class="detail-content">
  59. <section class="basic-info-section">
  60. <div class="section-heading">
  61. <h2>基本情報</h2>
  62. </div>
  63. <div class="info-form">
  64. <div class="info-row">
  65. <div class="info-label">世帯名</div>
  66. <div class="info-value">{{ danka.householdName }}</div>
  67. </div>
  68. <div class="info-row">
  69. <div class="info-label">世帯主</div>
  70. <div class="info-value">{{ danka.householder }}</div>
  71. </div>
  72. <div class="info-row">
  73. <div class="info-label">郵便番号</div>
  74. <div class="info-value">{{ danka.postalCode }}</div>
  75. </div>
  76. <div class="info-row">
  77. <div class="info-label">住所</div>
  78. <div class="info-value">{{ danka.address }}</div>
  79. </div>
  80. <div class="info-row phone-row">
  81. <div class="info-label">電話番号</div>
  82. <div class="phone-table">
  83. <div class="phone-header">
  84. <div>番号</div>
  85. <div>備考</div>
  86. </div>
  87. @for (phone of danka.phones; track $index) {
  88. <div class="phone-item">
  89. <div>{{ phone.tel }}</div>
  90. <div>{{ phone.note }}</div>
  91. </div>
  92. }
  93. </div>
  94. </div>
  95. </div>
  96. </section>
  97. <aside class="status-panel">
  98. <h2>この世帯の状況</h2>
  99. <div class="status-card-list">
  100. <button type="button" class="status-card" (click)="selectedTab = 'family'">
  101. <p class="status-label">家族</p>
  102. <p class="status-count">{{ families.length }} 名</p>
  103. <p class="status-link">詳細へ</p>
  104. </button>
  105. <button type="button" class="status-card" (click)="selectedTab = 'kakocho'">
  106. <p class="status-label">過去帳</p>
  107. <p class="status-count">{{ kakocholist.length }} 名</p>
  108. <p class="status-link">詳細へ</p>
  109. </button>
  110. </div>
  111. <section class="next-memorial">
  112. <h3>次の法要</h3>
  113. <div class="memorial-card">
  114. @if (nextMemorial) {
  115. <p class="memorial-title">
  116. {{ nextMemorial.name }} - {{ nextMemorial.memorialType }}
  117. </p>
  118. <p class="memorial-text">
  119. 対象年 {{ nextMemorial.targetYear }} / 没年月日 {{ nextMemorial.deathDate }}
  120. </p>
  121. } @else {
  122. <p class="memorial-title">対象の法要はありません</p>
  123. <p class="memorial-text">
  124. 今年の年忌法要対象者がいる場合に表示されます。
  125. </p>
  126. }
  127. </div>
  128. </section>
  129. </aside>
  130. </div>
  131. }
  132. @if (selectedTab === 'family') {
  133. <section class="family-tab-content">
  134. <div class="section-heading">
  135. <h2>家族情報</h2>
  136. </div>
  137. <section class="family-list-summary">
  138. <div>
  139. <p class="family-list-title">
  140. {{ danka.householdName }}の家族 {{ families.length }}名
  141. </p>
  142. </div>
  143. <p class="family-list-head">
  144. ※ 世帯主:{{ danka.householder }}
  145. </p>
  146. </section>
  147. <section class="family-table-section">
  148. @if (families.length > 0) {
  149. <div class="family-table">
  150. <div class="family-table-header">
  151. <div>氏名</div>
  152. <div>ふりがな</div>
  153. <div>生年月日</div>
  154. <div>年齢</div>
  155. <div>続柄</div>
  156. <div>備考</div>
  157. <div>操作</div>
  158. </div>
  159. @for (family of families; track family.id) {
  160. <div class="family-table-row">
  161. <div class="family-person-name">
  162. {{ family.name }}
  163. </div>
  164. <div>
  165. {{ family.furigana }}
  166. </div>
  167. <div>
  168. {{ family.birthDate || '未登録' }}
  169. </div>
  170. <div>
  171. {{ getAge(family.birthDate) }}
  172. </div>
  173. <div>
  174. {{ family.relationship || '未登録' }}
  175. </div>
  176. <div>
  177. {{ family.note || '' }}
  178. </div>
  179. <div class="family-table-action">
  180. <a class="family-edit-link" [routerLink]="['/danka', danka.id, 'family', family.id, 'edit']">
  181. 編集
  182. </a>
  183. </div>
  184. </div>
  185. }
  186. </div>
  187. } @else {
  188. <div class="empty-family-message">
  189. 登録されている家族情報はありません。
  190. </div>
  191. }
  192. </section>
  193. </section>
  194. }
  195. @if (selectedTab === 'kakocho') {
  196. <section class="coming-soon-section">
  197. <div class="section-heading">
  198. <h2>過去帳</h2>
  199. </div>
  200. <p class="empty-family-message">
  201. @if (kakocholist.length > 0) {
  202. <div class="family-table">
  203. <div class="family-table-header">
  204. <div>戒名</div>
  205. <div>俗名</div>
  206. <div>没年月日</div>
  207. <div>続柄</div>
  208. <div>回忌</div>
  209. <div>備考</div>
  210. </div>
  211. @for (kakocho of kakocholist; track kakocho.id) {
  212. <div class="family-table-row">
  213. <div class="family-person-name">
  214. {{ kakocho.kaimyo }}
  215. </div>
  216. <div>
  217. {{ kakocho.name }}
  218. </div>
  219. <div>
  220. {{ formatDeathDateWithYear(kakocho.deathDate) }}
  221. </div>
  222. <div>
  223. {{ kakocho.relationship }}
  224. </div>
  225. <div>
  226. {{ getKaiki(kakocho.deathDate) }}回忌
  227. </div>
  228. <div>
  229. {{ kakocho.note || '' }}
  230. </div>
  231. </div>
  232. }
  233. </div>
  234. } @else {
  235. <div class="empty-family-message">
  236. 登録されている家族情報はありません。
  237. </div>
  238. }
  239. </p>
  240. </section>
  241. }
  242. @if (selectedTab === 'familyTree') {
  243. <section class="family-tree-tab-content">
  244. <div class="section-heading">
  245. <h2>家系図</h2>
  246. <p>
  247. 家族情報に登録された親子・配偶者の関係をもとに表示します。
  248. </p>
  249. </div>
  250. @if (families.length > 0) {
  251. <div class="family-tree-layout">
  252. <section class="family-tree-area">
  253. <div class="family-tree-toolbar">
  254. <div>
  255. <p class="family-tree-title">
  256. {{ danka.householdName }}の家系図
  257. </p>
  258. <p class="family-tree-caption">
  259. 人物カードを選択すると、右側に関係情報が表示されます。
  260. </p>
  261. </div>
  262. <div class="family-tree-action-list">
  263. <button type="button" class="tree-action-button" [routerLink]="['/danka', danka.id, 'family-new']"
  264. [queryParams]="{relationMode: 'child', baseFamilyId: selectedFamily?.id}">
  265. 親子を追加
  266. </button>
  267. <button type="button" class="tree-action-button" [routerLink]="['/danka', danka.id, 'family-new']"
  268. [queryParams]="{relationMode: 'spouse', baseFamilyId: selectedFamily?.id}">
  269. 配偶者を追加
  270. </button>
  271. </div>
  272. </div>
  273. @if (selectedFamily) {
  274. <div class="family-tree-diagram">
  275. <div class="family-tree-svg-container">
  276. <svg #familyTreeSvg width="100%" height="100%" [attr.viewBox]="viewBox"
  277. preserveAspectRatio="xMidYMid meet">
  278. @for (
  279. layout of unitLayouts;
  280. track layout.node.unit.id
  281. ) {
  282. @for (
  283. child of layout.node.children;
  284. track child.unit.id
  285. ) {
  286. @if (getUnitLayout(child.unit.id); as childLayout) {
  287. <!-- 親から中間点 -->
  288. <line [attr.x1]="getCenterX(layout)" [attr.y1]="getBottomCenterY(layout)"
  289. [attr.x2]="getCenterX(layout)" [attr.y2]="getMiddleY(layout, childLayout)" stroke="black"
  290. stroke-width="2" />
  291. <!-- 横線 -->
  292. <line [attr.x1]="getCenterX(layout)" [attr.y1]="getMiddleY(layout, childLayout)"
  293. [attr.x2]="getCenterX(childLayout)" [attr.y2]="getMiddleY(layout, childLayout)" stroke="black"
  294. stroke-width="2" />
  295. <!-- 子へ -->
  296. <line [attr.x1]="getCenterX(childLayout)" [attr.y1]="getMiddleY(layout, childLayout)"
  297. [attr.x2]="getCenterX(childLayout)" [attr.y2]="getTopCenterY(childLayout)" stroke="black"
  298. stroke-width="2" />
  299. }
  300. }
  301. }
  302. @for (layout of unitLayouts; track layout.node.unit.id) {
  303. <!-- 夫婦線 -->
  304. @if (layout.node.unit.husband && layout.node.unit.wife) {
  305. <line [attr.x1]="layout.x + PERSON_WIDTH" [attr.y1]="layout.y + PERSON_HEIGHT / 2"
  306. [attr.x2]="layout.x + PERSON_WIDTH + SPOUSE_GAP" [attr.y2]="layout.y + PERSON_HEIGHT / 2"
  307. stroke="red" stroke-width="2" />
  308. }
  309. <!-- ========================= -->
  310. <!-- 夫 -->
  311. <!-- ========================= -->
  312. @if (layout.node.unit.husband) {
  313. <rect [attr.x]="getHusbandX(layout)" [attr.y]="layout.y" [attr.width]="PERSON_WIDTH"
  314. [attr.height]="PERSON_HEIGHT" fill="#dbeafe"
  315. [attr.stroke]="selectedFamily?.id === layout.node.unit.husband?.id ? '#2563eb' : 'black'"
  316. class="family-node" (click)="selectFamily(layout.node.unit.husband!)" />
  317. <!-- 右:名前(基準) -->
  318. <text [attr.x]="getHusbandTextX(layout)" [attr.y]="layout.y + 15"
  319. style="writing-mode: vertical-rl; text-orientation: upright;" class="family-text"
  320. (click)="selectFamily(layout.node.unit.husband!)">
  321. {{ layout.node.unit.husband.name }}
  322. </text>
  323. <!-- 中央:没年月日(名前の左) -->
  324. @if (getDeathWareki(layout.node.unit.husband)) {
  325. <text [attr.x]="getHusbandTextX(layout) - 26" [attr.y]="getDeathTextY(layout, getDeathWareki(layout.node.unit.husband))"
  326. dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
  327. [attr.font-size]="DEATH_FONT_SIZE">
  328. {{ getDeathWareki(layout.node.unit.husband) }}
  329. </text>
  330. }
  331. <!-- 左:享年 -->
  332. @if (getAgeAtDeathText(layout.node.unit.husband)) {
  333. <text [attr.x]="getHusbandTextX(layout) - 40" [attr.y]="getDeathTextY(layout, getAgeAtDeathText(layout.node.unit.husband))"
  334. dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
  335. [attr.font-size]="DEATH_FONT_SIZE">
  336. {{ getAgeAtDeathText(layout.node.unit.husband) }}
  337. </text>
  338. }
  339. }
  340. <!-- ========================= -->
  341. <!-- 妻 -->
  342. <!-- ========================= -->
  343. @if (layout.node.unit.wife) {
  344. <rect [attr.x]="getWifeX(layout)" [attr.y]="layout.y" [attr.width]="PERSON_WIDTH"
  345. [attr.height]="PERSON_HEIGHT" fill="#fde2e2"
  346. [attr.stroke]="selectedFamily?.id === layout.node.unit.wife?.id ? '#dc2626' : 'black'"
  347. class="family-node" (click)="selectFamily(layout.node.unit.wife!)" />
  348. <!-- 右:名前 -->
  349. <text [attr.x]="getWifeTextX(layout)" [attr.y]="layout.y + 15"
  350. style="writing-mode: vertical-rl; text-orientation: upright;" class="family-text"
  351. (click)="selectFamily(layout.node.unit.wife!)">
  352. {{ layout.node.unit.wife.name }}
  353. </text>
  354. <!-- 中央:没年月日 -->
  355. @if (getDeathWareki(layout.node.unit.wife)) {
  356. <text [attr.x]="getWifeTextX(layout) - 26" [attr.y]="getDeathTextY(layout, getDeathWareki(layout.node.unit.wife))"
  357. dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
  358. [attr.font-size]="DEATH_FONT_SIZE">
  359. {{ getDeathWareki(layout.node.unit.wife) }}
  360. </text>
  361. }
  362. <!-- 左:享年 -->
  363. @if (getAgeAtDeathText(layout.node.unit.wife)) {
  364. <text [attr.x]="getWifeTextX(layout) - 40" [attr.y]="getDeathTextY(layout, getAgeAtDeathText(layout.node.unit.wife))"
  365. dominant-baseline="text-after-edge" style="writing-mode: vertical-rl; text-orientation: upright;"
  366. [attr.font-size]="DEATH_FONT_SIZE">
  367. {{ getAgeAtDeathText(layout.node.unit.wife) }}
  368. </text>
  369. }
  370. }
  371. }
  372. </svg>
  373. </div>
  374. </div>
  375. <div class="family-tree-member-list">
  376. <p class="member-list-title">人物一覧</p>
  377. <div class="member-chip-list">
  378. @for (family of families; track family.id) {
  379. <button type="button" class="member-chip" [class.active]="selectedFamily.id === family.id"
  380. (click)="selectFamily(family)">
  381. {{ family.name }}
  382. </button>
  383. }
  384. </div>
  385. </div>
  386. } @else {
  387. <p class="empty-family-message">
  388. 人物カードを選択してください。
  389. </p>
  390. }
  391. </section>
  392. <aside class="family-tree-side-panel">
  393. <h3>選択中の人物情報</h3>
  394. @if (selectedFamily) {
  395. <div class="selected-person-card">
  396. <p class="selected-person-name">
  397. {{ selectedFamily.name }}
  398. </p>
  399. <p class="selected-person-sub">
  400. {{ selectedFamily.furigana || 'ふりがな未登録' }}
  401. </p>
  402. </div>
  403. <div class="selected-info-list">
  404. <div class="selected-info-row">
  405. <span>続柄</span>
  406. <strong>{{ selectedFamily.relationship || '未登録' }}</strong>
  407. </div>
  408. <div class="selected-info-row">
  409. <span>生年月日</span>
  410. <strong>{{ selectedFamily.birthDate || '未登録' }}</strong>
  411. </div>
  412. <div class="selected-info-row">
  413. <span>父</span>
  414. <strong>{{ getFather(selectedFamily)?.name || '未登録' }}</strong>
  415. </div>
  416. <div class="selected-info-row">
  417. <span>母</span>
  418. <strong>{{ getMother(selectedFamily)?.name || '未登録' }}</strong>
  419. </div>
  420. <div class="selected-info-row">
  421. <span>配偶者</span>
  422. <strong>{{ getSpouse(selectedFamily)?.name || '未登録' }}</strong>
  423. </div>
  424. <div class="selected-info-row children-row">
  425. <span>子</span>
  426. <strong>
  427. @if (getChildren(selectedFamily).length > 0) {
  428. @for (child of getChildren(selectedFamily); track child.id) {
  429. <span class="child-name">
  430. {{ child.name }}
  431. </span>
  432. }
  433. } @else {
  434. 未登録
  435. }
  436. </strong>
  437. </div>
  438. </div>
  439. <section class="marriage-summary-panel">
  440. <h4>配偶関係</h4>
  441. <div class="marriage-summary-block">
  442. <p class="marriage-summary-label">現在の配偶者</p>
  443. @if (getCurrentMarriage(selectedFamily); as currentMarriage) {
  444. @if (getMarriagePartner(currentMarriage, selectedFamily); as currentPartner) {
  445. <button type="button" class="marriage-person-button" (click)="selectFamily(currentPartner)">
  446. {{ currentPartner.name }}
  447. </button>
  448. } @else {
  449. <p class="marriage-empty-text">未登録</p>
  450. }
  451. } @else {
  452. <p class="marriage-empty-text">未登録</p>
  453. }
  454. </div>
  455. <div class="marriage-summary-block">
  456. <p class="marriage-summary-label">過去の配偶者</p>
  457. @if (getPastMarriages(selectedFamily).length > 0) {
  458. <div class="marriage-history-list">
  459. @for (relation of getPastMarriages(selectedFamily); track relation.id) {
  460. @if (getMarriagePartner(relation, selectedFamily); as pastPartner) {
  461. <button type="button" class="marriage-person-button secondary" (click)="selectFamily(pastPartner)">
  462. <span>{{ pastPartner.name }}</span>
  463. <small>{{ getMarriageStatusLabel(relation.status) }}</small>
  464. </button>
  465. }
  466. }
  467. </div>
  468. } @else {
  469. <p class="marriage-empty-text">未登録</p>
  470. }
  471. </div>
  472. </section>
  473. <div class="selected-person-actions">
  474. <a class="selected-person-button"
  475. [routerLink]="['/danka', danka.id, 'family', selectedFamily.id, 'edit']">
  476. 個人情報を編集
  477. </a>
  478. <button type="button" class="selected-person-button secondary" (click)="selectedTab = 'kakocho'">
  479. 過去帳を確認
  480. </button>
  481. </div>
  482. } @else {
  483. <p class="empty-family-message">
  484. 人物カードを選択してください。
  485. </p>
  486. }
  487. </aside>
  488. </div>
  489. } @else {
  490. <div class="empty-family-message">
  491. 登録されている家族情報はありません。
  492. </div>
  493. }
  494. </section>
  495. }
  496. } @else {
  497. <p class="empty-family-message">
  498. 檀家情報が見つかりませんでした。
  499. </p>
  500. }
  501. </section>
  502. </main>
  503. </div>