xref: /webtrees/resources/views/lists/individuals-table.phtml (revision 2e2147f5ad3d7a49690d1d415da900eaaed2f612)
1<?php
2
3declare(strict_types=1);
4
5use Fisharebest\Webtrees\Age;
6use Fisharebest\Webtrees\Auth;
7use Fisharebest\Webtrees\Date;
8use Fisharebest\Webtrees\I18N;
9use Fisharebest\Webtrees\Individual;
10use Fisharebest\Webtrees\Module\ModuleChartInterface;
11use Fisharebest\Webtrees\Module\ModuleInterface;
12use Fisharebest\Webtrees\Module\RelationshipsChartModule;
13use Fisharebest\Webtrees\Registry;
14use Fisharebest\Webtrees\Services\ModuleService;
15use Fisharebest\Webtrees\Tree;
16use Fisharebest\Webtrees\View;
17use Illuminate\Support\Collection;
18
19/**
20 * @var Collection<int,Individual> $individuals
21 * @var bool                       $sosa
22 * @var Tree                       $tree
23 */
24
25// lists require a unique ID in case there are multiple lists per page
26$table_id             = Registry::idFactory()->id();
27$today_jd             = Registry::timestampFactory()->now()->julianDay();
28$hundred_years_ago    = Registry::timestampFactory()->now()->subtractYears(100)->julianDay();
29$show_estimated_dates = (bool) $tree->getPreference('SHOW_EST_LIST_DATES');
30$today                = new Date(strtoupper(date('d M Y')));
31
32$module = Registry::container()->get(ModuleService::class)
33    ->findByComponent(ModuleChartInterface::class, $tree, Auth::user())
34    ->first(static function (ModuleInterface $module) {
35        return $module instanceof RelationshipsChartModule;
36    });
37?>
38
39<?php View::push('javascript') ?>
40<script>
41$("#<?= e($table_id) ?> > .wt-table-individual").dataTable({
42    processing: true,
43    retrieve: true,
44    columns: [
45        /* Given names  */ { type: "text" },
46        /* Surnames     */ { type: "text" },
47        /* SOSA number  */ { type: "num", visible: <?= json_encode($sosa, JSON_THROW_ON_ERROR) ?> },
48        /* Birth date   */ { type: "num" },
49        /* Anniversary  */ { type: "num" },
50        /* Birthplace   */ { type: "text" },
51        /* Children     */ { type: "num" },
52        /* Deate date   */ { type: "num" },
53        /* Anniversary  */ { type: "num" },
54        /* Age          */ { type: "num" },
55        /* Death place  */ { type: "text" },
56        /* Last change  */ { visible: <?= json_encode($tree->getPreference('SHOW_LAST_CHANGE'), JSON_THROW_ON_ERROR) ?> },
57        /* Filter sex   */ { sortable: false },
58        /* Filter birth */ { sortable: false },
59        /* Filter death */ { sortable: false },
60        /* Filter tree  */ { sortable: false }
61    ],
62    sorting: <?= json_encode($sosa ? [[4, 'asc']] : [[1, 'asc']], JSON_THROW_ON_ERROR) ?>
63});
64
65$("#<?= e($table_id) ?>")
66    /* Hide/show parents */
67    .on("click", "#btn-toggle-parents", function() {
68        $(".wt-individual-list-parents").slideToggle();
69    })
70    /* Filter buttons in table header */
71    .on("click", "input[data-filter-column]", function() {
72        let checkbox = $(this);
73
74        // Deselect other options
75        let siblings = checkbox.siblings("input[type='checkbox']");
76        siblings.prop("checked", false).removeAttr("checked");
77
78        // Apply (or clear) this filter
79        let checked = checkbox.prop("checked");
80        let filter  = checked ? checkbox.data("filter-value") : "";
81        let column  = $("#<?= e($table_id) ?> .wt-table-individual").DataTable().column(checkbox.data("filter-column"));
82        column.search(filter).draw();
83    });
84</script>
85<?php View::endpush() ?>
86
87<div id="<?= e($table_id) ?>">
88    <table class="table table-bordered table-sm wt-table-individual"
89        <?= view('lists/datatables-attributes') ?>
90    >
91        <thead>
92            <tr>
93                <th colspan="16">
94                    <div class="btn-toolbar d-flex justify-content-between mb-2" role="toolbar">
95                        <div class="btn-group btn-group-sm" role="group">
96                            <input id="<?= e($table_id) ?>-bg-sex-M" class="btn-check" type="checkbox" data-filter-column="12" data-filter-value="M" autocomplete="off">
97                            <label for="<?= e($table_id) ?>-bg-sex-M" class="btn btn-outline-secondary" title="<?= I18N::translate('Show only males.') ?>">
98                                <?= view('icons/sex', ['sex' => 'M']) ?>
99                            </label>
100
101                            <input id="<?= e($table_id) ?>-bg-sex-F" class="btn-check" type="checkbox" data-filter-column="12" data-filter-value="F" autocomplete="off">
102                            <label for="<?= e($table_id) ?>-bg-sex-F" class="btn btn-outline-secondary" title="<?= I18N::translate('Show only females.') ?>">
103                                <?= view('icons/sex', ['sex' => 'F']) ?>
104                            </label>
105
106                            <input id="<?= e($table_id) ?>-bg-sex-U" class="btn-check" type="checkbox" data-filter-column="12" data-filter-value="U" autocomplete="off">
107                            <label for="<?= e($table_id) ?>-bg-sex-U" class="btn btn-outline-secondary" title="<?= I18N::translate('Show only individuals for whom the gender is not known.') ?>">
108                                <?= view('icons/sex', ['sex' => 'U']) ?>
109                            </label>
110                        </div>
111
112                        <div class="btn-group btn-group-sm" role="group">
113                            <input id="<?= e($table_id) ?>-bg-dead-N" class="btn-check" type="checkbox" data-filter-column="14" data-filter-value="N" autocomplete="off">
114                            <label for="<?= e($table_id) ?>-bg-dead-N" class="btn btn-outline-secondary" title="<?= I18N::translate('Show individuals who are alive or couples where both partners are alive.') ?>">
115                                <?= I18N::translate('Alive') ?>
116                            </label>
117
118                            <input id="<?= e($table_id) ?>-bg-dead-Y" class="btn-check" type="checkbox" data-filter-column="14" data-filter-value="Y" autocomplete="off">
119                            <label for="<?= e($table_id) ?>-bg-dead-Y" class="btn btn-outline-secondary" title="<?= I18N::translate('Show individuals who are dead or couples where both partners are dead.') ?>">
120                                <?= I18N::translate('Dead') ?>
121                            </label>
122
123                            <input id="<?= e($table_id) ?>-bg-dead-YES" class="btn-check" type="checkbox" data-filter-column="14" data-filter-value="YES" autocomplete="off">
124                            <label for="<?= e($table_id) ?>-bg-dead-YES" class="btn btn-outline-secondary" title="<?= I18N::translate('Show individuals who died more than 100 years ago.') ?>">
125                                <?= I18N::translate('Death') ?>&gt;100
126                            </label>
127
128                            <input id="<?= e($table_id) ?>-bg-alive-Y100" class="btn-check" type="checkbox" data-filter-column="14" data-filter-value="Y100" autocomplete="off">
129                            <label for="<?= e($table_id) ?>-bg-alive-Y100" class="btn btn-outline-secondary" title="<?= I18N::translate('Show individuals who died within the last 100 years.') ?>">
130                                <?= I18N::translate('Death') ?>&lt;=100
131                            </label>
132                        </div>
133
134                        <div class="btn-group btn-group-sm" role="group">
135
136                            <input id="<?= e($table_id) ?>-bg-born-YES" class="btn-check" type="checkbox" data-filter-column="13" data-filter-value="YES" autocomplete="off">
137                            <label for="<?= e($table_id) ?>-bg-born-YES" class="btn btn-outline-secondary" title="<?= I18N::translate('Show individuals born more than 100 years ago.') ?>">
138                                <?= I18N::translate('Birth') ?>&gt;100
139                            </label>
140
141                            <input id="<?= e($table_id) ?>-bg-born-Y100" class="btn-check" type="checkbox" data-filter-column="13" data-filter-value="Y100" autocomplete="off">
142                            <label for="<?= e($table_id) ?>-bg-born-Y100" class="btn btn-outline-secondary" title="<?= I18N::translate('Show individuals born within the last 100 years.') ?>">
143                                <?= I18N::translate('Birth') ?>&lt;=100
144                            </label>
145                        </div>
146
147                        <div class="btn-group btn-group-sm" role="group">
148
149                            <input id="<?= e($table_id) ?>-bg-roots-R" class="btn-check" type="checkbox" data-filter-column="15" data-filter-value="R" autocomplete="off">
150                            <label for="<?= e($table_id) ?>-bg-roots-R" class="btn btn-outline-secondary" title="<?= I18N::translate('Show “roots” couples or individuals. These individuals may also be called “patriarchs”. They are individuals who have no parents recorded in the database.') ?>">
151                                <?= I18N::translate('Roots') ?>
152                            </label>
153
154                            <input id="<?= e($table_id) ?>-bg-roots-L" class="btn-check" type="checkbox" data-filter-column="15" data-filter-value="L" autocomplete="off">
155                            <label for="<?= e($table_id) ?>-bg-roots-L" class="btn btn-outline-secondary" title="<?= I18N::translate('Show “leaves” couples or individuals. These are individuals who are alive but have no children recorded in the database.') ?>">
156                                <?= I18N::translate('Leaves') ?>
157                            </label>
158                        </div>
159                    </div>
160                </th>
161            </tr>
162            <tr>
163                <th><?= I18N::translate('Given names') ?></th>
164                <th><?= I18N::translate('Surname') ?></th>
165                <th><?= /* I18N: Abbreviation for “Sosa-Stradonitz number”. This is an individual’s surname, so may need transliterating into non-latin alphabets. */
166                    I18N::translate('Sosa') ?></th>
167                <th><?= I18N::translate('Birth') ?></th>
168                <th>
169                    <span title="<?= I18N::translate('Anniversary') ?>">
170                        <?= view('icons/anniversary') ?>
171                    </span>
172                </th>
173                <th><?= I18N::translate('Place') ?></th>
174                <th>
175                    <i class="icon-children" title="<?= I18N::translate('Children') ?>"></i>
176                </th>
177                <th><?= I18N::translate('Death') ?></th>
178                <th>
179                    <span title="<?= I18N::translate('Anniversary') ?>">
180                        <?= view('icons/anniversary') ?>
181                    </span>
182                </th>
183                <th><?= I18N::translate('Age') ?></th>
184                <th><?= I18N::translate('Place') ?></th>
185                <th><?= I18N::translate('Last change') ?></th>
186                <th hidden></th>
187                <th hidden></th>
188                <th hidden></th>
189                <th hidden></th>
190            </tr>
191        </thead>
192
193        <tbody>
194            <?php foreach ($individuals as $key => $individual) : ?>
195                <tr class="<?= $individual->isPendingAddition() ? 'wt-new' : '' ?> <?= $individual->isPendingDeletion() ? 'wt-old' : '' ?>">
196                    <td colspan="2" data-sort="<?= e(str_replace([',', Individual::PRAENOMEN_NESCIO, Individual::NOMEN_NESCIO], 'AAAA', implode(',', array_reverse(explode(',', $individual->sortName()))))) ?>">
197                        <?php foreach ($individual->getAllNames() as $num => $name) : ?>
198                            <div>
199                                <a title="<?= $name['type'] === '_MARNM' ? I18N::translate('Married name') :  '' ?>" href="<?= e($individual->url()) ?>" class="<?= $num === $individual->getPrimaryName() ? '' : 'text-muted' ?>">
200                                    <?= $name['full'] ?>
201                                </a>
202                                <?php if ($num === $individual->getPrimaryName()) : ?>
203                                    <small><?= view('icons/sex', ['sex' => $individual->sex()]) ?></small>
204                                <?php endif ?>
205                            </div>
206                        <?php endforeach ?>
207                        <?= view('lists/individual-table-parents', ['individual' => $individual]) ?>
208                    </td>
209
210                    <td hidden data-sort="<?= e(str_replace([',', Individual::PRAENOMEN_NESCIO, Individual::NOMEN_NESCIO], 'AAAA', $individual->sortName())) ?>"></td>
211
212                    <td class="text-center" data-sort="<?= $key ?>">
213                        <?php if ($sosa) : ?>
214                            <?php if ($module instanceof RelationshipsChartModule) : ?>
215                                <a href="<?= e($module->chartUrl($individuals[1], ['xref2' => $individual->xref()])) ?>" rel="nofollow" title="<?= I18N::translate('Relationships') ?>" rel="nofollow">
216                                    <?= I18N::number($key) ?>
217                                </a>
218                            <?php else : ?>
219                                <?= I18N::number($key) ?>
220                            <?php endif ?>
221                        <?php endif ?>
222                    </td>
223
224                    <!-- Birthdate -->
225                    <?php $estimated_birth_date = $individual->getEstimatedBirthDate(); ?>
226
227                    <td data-sort="<?= $estimated_birth_date->julianDay() ?>">
228                        <?php $birth_dates = $individual->getAllBirthDates(); ?>
229
230                        <?php foreach ($birth_dates as $birth_date) : ?>
231                            <div><?= $birth_date->display($tree, null, true) ?></div>
232                        <?php endforeach ?>
233
234                        <?php if ($birth_dates === [] && $show_estimated_dates) : ?>
235                            <?= $estimated_birth_date->display($tree, null, true) ?>
236                        <?php endif ?>
237                    </td>
238
239                    <!-- Birth anniversary -->
240                    <td class="text-center" data-sort="<?= - $estimated_birth_date->julianDay() ?>">
241                        <?= (new Age($birth_dates[0] ?? new Date(''), $today))->ageYearsString() ?>
242                    </td>
243
244                    <!-- birthplace -->
245                    <td data-sort="<?= e($individual->getBirthPlace()->gedcomName()) ?>">
246                        <?php foreach ($individual->getAllBirthPlaces() as $birth_place) : ?>
247                            <div><?= $birth_place->shortName(true) ?></div>
248                        <?php endforeach ?>
249                    </td>
250
251                    <!-- Number of children -->
252                    <td class="text-center" data-sort="<?= $individual->numberOfChildren() ?>">
253                        <?= I18N::number($individual->numberOfChildren()) ?>
254                    </td>
255
256                    <!-- Death date -->
257                    <?php $death_dates = $individual->getAllDeathDates() ?>
258                    <td data-sort="<?= $individual->getEstimatedDeathDate()->julianDay() ?>">
259                        <?php foreach ($death_dates as $num => $death_date) : ?>
260                            <div><?= $death_date->display($tree, null, true) ?></div>
261                        <?php endforeach ?>
262
263                        <?php if ($death_dates === [] && $show_estimated_dates && $individual->getEstimatedDeathDate()->minimumDate()->minimumJulianDay() < $today_jd) : ?>
264                            <?= $individual->getEstimatedDeathDate()->display($tree, null, true) ?>
265                        <?php endif ?>
266                    </td>
267
268                    <!-- Death anniversary -->
269                    <td class="text-center" data-sort="<?= - $individual->getEstimatedDeathDate()->julianDay() ?>">
270                        <?= (new Age($death_dates[0] ?? new Date(''), $today))->ageYearsString() ?>
271                    </td>
272
273                    <!-- Age at death -->
274                    <?php $age = new Age($birth_dates[0] ?? new Date(''), $death_dates[0] ?? new Date('')) ?>
275                    <td class="text-center" data-sort="<?= $age->ageDays() ?>">
276                        <?= $age->ageYearsString() ?>
277                    </td>
278
279                    <!-- Death place -->
280                    <td data-sort="<?= e($individual->getDeathPlace()->gedcomName()) ?>">
281                        <?php foreach ($individual->getAllDeathPlaces() as $death_place) : ?>
282                            <div><?= $death_place->shortName(true) ?></div>
283                        <?php endforeach ?>
284                    </td>
285
286                    <!-- Last change -->
287                    <td data-sort="<?= $individual->lastChangeTimestamp()->timestamp() ?>">
288                        <?= view('components/datetime', ['timestamp' => $individual->lastChangeTimestamp()]) ?>
289                    </td>
290
291                    <!-- Filter by sex -->
292                    <td hidden>
293                        <?= $individual->sex() ?>
294                    </td>
295
296                    <!-- Filter by birthdate -->
297                    <td hidden>
298                        <?php if ($estimated_birth_date->maximumJulianDay() > $hundred_years_ago && $estimated_birth_date->maximumJulianDay() <= $today_jd) : ?>
299                            Y100
300                        <?php else : ?>
301                            YES
302                        <?php endif ?>
303                    </td>
304
305                    <!-- Filter by death date -->
306                    <td hidden>
307                        <?php if ($individual->getEstimatedDeathDate()->maximumJulianDay() > $hundred_years_ago && $individual->getEstimatedDeathDate()->maximumJulianDay() <= $today_jd) : ?>
308                            Y100
309                        <?php elseif ($individual->isDead()) : ?>
310                            YES
311                        <?php else : ?>
312                            N
313                        <?php endif ?>
314                    </td>
315
316                    <!-- Filter by roots/leaves -->
317                    <td hidden>
318                        <?php if ($individual->childFamilies()->isEmpty()) : ?>
319                            R
320                        <?php elseif (!$individual->isDead() && $individual->numberOfChildren() < 1) : ?>
321                            L
322                        <?php endif ?>
323                    </td>
324                </tr>
325            <?php endforeach ?>
326        </tbody>
327
328        <tfoot>
329            <tr>
330                <th colspan="16">
331                    <div class="btn-group btn-group-sm">
332                        <input type="checkbox" class="btn-check" id="btn-toggle-parents" data-wt-persist="individuals-parents" autocomplete="off">
333                        <label class="btn btn-secondary" for="btn-toggle-parents">
334                            <?= I18N::translate('Show parents') ?>
335                        </label>
336                    </div>
337                </th>
338            </tr>
339        </tfoot>
340    </table>
341</div>
342
343