mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 00:07:00 +01:00
The UI was uneven: the home page looked finished, but app pages used three different display typefaces, the anonymize flow changed frame between steps, and the dashboard, explorer and admin screens each had their own controls, status colours and edge-case handling. Make every screen follow one system: shared page header, grid, buttons, filters and status colours; a repository picker and step trail on anonymize; a "What reviewers will see" preview of the actual reviewer page; consistent file headers and viewers for every file type; truthful admin charts and tables. Fold the accumulated override blocks back into style.css (22% smaller, no visual change). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
11 KiB
HTML
117 lines
11 KiB
HTML
<div class="container paper-page admin-page">
|
|
<div class="paper-crumbs">Admin / <span class="here">Conferences</span></div>
|
|
<h1 class="paper-page-title">Conferences</h1>
|
|
|
|
<nav class="admin-nav">
|
|
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
|
|
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
|
|
<a href="/admin/users"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
|
|
<a href="/admin/conferences" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
|
|
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
|
|
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
|
|
</nav>
|
|
|
|
<div class="seg-bar">
|
|
<div class="seg-filter" role="radiogroup" aria-label="Filter by status">
|
|
<button type="button" role="radio" class="seg-chip" :class="{ active: segView() === 'all' }" :aria-checked="segView() === 'all'" @click="setSegView('all')">All <span class="count">{{ fmt?.number(segCount('all')) }}</span></button>
|
|
<button type="button" role="radio" class="seg-chip" :class="{ active: segView() === 'ready', 'is-empty': !segCount('ready') }" :aria-checked="segView() === 'ready'" @click="setSegView('ready')"><span class="seg-dot ok" aria-hidden="true"></span>Ready <span class="count">{{ fmt?.number(segCount('ready')) }}</span></button>
|
|
<button type="button" role="radio" class="seg-chip" :class="{ active: segView() === 'preparing', 'is-empty': !segCount('preparing') }" :aria-checked="segView() === 'preparing'" @click="setSegView('preparing')"><span class="seg-dot warn" aria-hidden="true"></span>Preparing <span class="count">{{ fmt?.number(segCount('preparing')) }}</span></button>
|
|
<button type="button" role="radio" class="seg-chip" :class="{ active: segView() === 'error', 'is-empty': !segCount('error') }" :aria-checked="segView() === 'error'" @click="setSegView('error')"><span class="seg-dot bad" aria-hidden="true"></span>Error <span class="count">{{ fmt?.number(segCount('error')) }}</span></button>
|
|
<button type="button" role="radio" class="seg-chip" :class="{ active: segView() === 'expired', 'is-empty': !segCount('expired') }" :aria-checked="segView() === 'expired'" @click="setSegView('expired')"><span class="seg-dot off" aria-hidden="true"></span>Expired <span class="count">{{ fmt?.number(segCount('expired')) }}</span></button>
|
|
<button type="button" role="radio" class="seg-chip" :class="{ active: segView() === 'removed', 'is-empty': !segCount('removed') }" :aria-checked="segView() === 'removed'" @click="setSegView('removed')"><span class="seg-dot off" aria-hidden="true"></span>Removed <span class="count">{{ fmt?.number(segCount('removed')) }}</span></button>
|
|
</div>
|
|
<p class="seg-countline" role="status"><strong>{{ total >= 0 ? fmt.number(total) : '…' }}</strong> shown</p>
|
|
</div>
|
|
|
|
<div class="alert alert-danger" style="margin: 8px 0;" v-if="fetchError">
|
|
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> {{ fetchError }}
|
|
</div>
|
|
|
|
<form class="w-100 admin-filter-toolbar" aria-label="Conferences" accept-charset="UTF-8" v-form="viewState">
|
|
<div class="admin-filter-row">
|
|
<div class="search-wrap">
|
|
<input type="search" class="form-control" placeholder="Search conferences…" autocomplete="off" v-field="{ state: viewState, set: value => { query.search = value }, value: query?.search, form: null, options: {} }">
|
|
<span class="admin-search-hint" v-if="!query?.search">/</span>
|
|
</div>
|
|
<span class="admin-filter-spacer"></span>
|
|
<button class="btn btn-sm" type="button" v-if="total > 0" @click="exportCsv()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-export"></use></svg> Export</button>
|
|
<span class="admin-filter-inline" aria-label="Pagination" v-if="totalPage > 1">
|
|
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page <= 1" aria-label="Previous page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg></button>
|
|
<span style="font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted);">{{ query?.page }}/{{ totalPage || 1 }}</span>
|
|
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page >= totalPage" aria-label="Next page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="admin-filter-row" v-if="chips?.length">
|
|
<div class="admin-active-chips">
|
|
<span class="admin-active-chip" v-for="(chip, index) in chips" :key="chip?.key">
|
|
<span class="key">{{ chip?.label }}</span>
|
|
<span>{{ chip?.value }}</span>
|
|
<button type="button" @click="clearFilter(chip.key)" aria-label="Clear filter"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg></button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="paper-table paper-table-conferences w-100" role="table" aria-label="Conferences">
|
|
<div class="paper-table-head" role="row" v-if="total !== 0">
|
|
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == 'name'}" @click="sortBy('name')">Conference <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon('name')" :class="{ascending: sortIcon('name') === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
|
|
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == 'status'}" @click="sortBy('status')">Status <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon('status')" :class="{ascending: sortIcon('status') === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
|
|
<div role="columnheader" class="num">Repos</div>
|
|
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == 'startDate'}" @click="sortBy('startDate')">Window <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon('startDate')" :class="{ascending: sortIcon('startDate') === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
|
|
<div role="columnheader" aria-label="Actions"></div>
|
|
</div>
|
|
<div class="paper-table-row" role="row" v-for="(conference, index) in filteredConferences">
|
|
<div class="cell-anon" role="cell">
|
|
<svg class="ui-icon record-type-icon" role="img" aria-label="Conference"><use href="#project-icon-conference"></use></svg>
|
|
<div class="anon-text">
|
|
<a class="repo-name" v-text="conference?.name" :href="safeUrl("/conference/" + (conference?.conferenceID))"></a>
|
|
<div class="anon-sub">
|
|
<span>{{ conference?.conferenceID }}</span><span>{{ fmt?.money(conference?.price || 0) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cell-status" role="cell">
|
|
<span class="status-dot" :class="'st-' + (conference?.status || 'unknown')"></span>
|
|
<span v-text="fmt?.statusLabel(conference?.status)"></span>
|
|
</div>
|
|
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg>
|
|
<a title="Show repositories in this conference" v-text="fmt?.number(conference?.repositories?.length || 0)" :href="safeUrl("/admin/?conference=" + (conference?.conferenceID))"></a>
|
|
</div>
|
|
<div class="cell-expires" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg> {{ fmt?.date(conference?.startDate) }} – {{ fmt?.date(conference?.endDate) }}</div>
|
|
<div class="cell-actions" role="cell">
|
|
<div class="dropdown">
|
|
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Actions">
|
|
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
|
|
</button>
|
|
<div class="dropdown-menu dropdown-menu-right">
|
|
<a class="dropdown-item" :href="safeUrl("/conference/" + (conference?.conferenceID) + "/edit")"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit</a>
|
|
<a class="dropdown-item" :href="safeUrl("/conference/" + (conference?.conferenceID) + "/")"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View</a>
|
|
<a class="dropdown-item" :href="safeUrl("/admin/?conference=" + (conference?.conferenceID))"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> View repositories</a>
|
|
<div class="dropdown-divider"></div>
|
|
<a class="dropdown-item text-danger" href="#" v-show="conference?.status != 'removed'" @click.prevent="removeConference(conference)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="paper-table-empty" v-if="filteredConferences?.length == 0">
|
|
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
|
|
<span>{{ total ? 'No conferences match the current filters.' : 'No conferences yet.' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-toolbar" style="justify-content: space-between; border-bottom: none;">
|
|
<span aria-hidden="true"></span>
|
|
<div class="pagination-compact" v-if="totalPage > 1">
|
|
<button class="btn btn-sm" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page <= 1"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg> Previous</button>
|
|
<input type="number" class="form-control form-control-sm" min="1" style="width: 56px;" v-field="{ state: viewState, set: value => { query.page = value }, value: query?.page, form: null, options: {} }" :max="(totalPage)">
|
|
<span>of {{ totalPage }}</span>
|
|
<button class="btn btn-sm" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page >= totalPage">Next <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
|
|
</div>
|
|
<span class="admin-filter-inline">
|
|
<label>Per page</label>
|
|
<select class="form-control form-control-sm" v-field="{ state: viewState, set: value => { query.limit = value }, value: query?.limit, form: null, options: {} }"><option value="10">10</option><option value="25">25</option><option value="50">50</option><option value="100">100</option><option value="250">250</option></select>
|
|
</span>
|
|
</div>
|
|
</div>
|