@webx-ui/module-vacancies 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/VacanciesPage.d.ts +22 -0
- package/dist/VacancyCreateDialog.d.ts +4 -0
- package/dist/VacancyEditorPage.d.ts +21 -0
- package/dist/VacancyHistory.d.ts +2 -0
- package/dist/api.d.ts +47 -0
- package/dist/days.d.ts +22 -0
- package/dist/editor.d.ts +19 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1296 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +13 -0
- package/dist/module.d.ts +22 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +104 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/api.ts","../src/days.ts","../src/messages.ts","../src/i18n.ts","../src/VacancyCreateDialog.vue","../src/VacanciesPage.vue","../src/editor.ts","../src/VacancyEditorPage.vue","../src/VacancyHistory.vue","../src/module.ts"],"sourcesContent":["import { reorderItems, type AdminContext } from '@webx-ui/module-admin'\nimport type {\n VacanciesList,\n VacancyDetail,\n VacancyInput,\n VacancyQuery,\n VacancyRow,\n VacancySave,\n VacancyVersion,\n} from './types'\n\nexport interface VacanciesApi {\n /** Every vacancy the query asks for, with what the list can be narrowed by. */\n list(query?: VacancyQuery): Promise<VacanciesList>\n /** One vacancy as its editor opens it: the record, the screen's values and the revision. */\n get(id: number): Promise<VacancyDetail>\n /** A new vacancy, made in one transaction: a refused address leaves no bare row behind. */\n create(input: VacancyInput): Promise<VacancyDetail>\n /**\n * Save the draft. Refused with a 409 when the revision is stale — the body is a\n * {@link VacancyConflict} with the vacancy as it now is.\n */\n save(id: number, input: VacancySave): Promise<VacancyDetail>\n /** Throw away what is waiting and keep what the site is showing. */\n discard(id: number): Promise<VacancyDetail>\n /**\n * A copy as a draft that was never published (decision 20): every field, the categories and\n * the form, the same title and an address with the next free suffix, right after the original.\n * The answer is the copy's form, so the editor can open it straight away.\n */\n duplicate(id: number): Promise<VacancyDetail>\n publish(id: number): Promise<VacancyRow>\n unpublish(id: number): Promise<VacancyRow>\n /**\n * The hiring is over, before its last day — or it is on again. A publication of its own on the\n * server (`is_closed` is published with everything else), so it is refused with a 409 while\n * edits are waiting: closing would put them on the site too.\n */\n close(id: number): Promise<VacancyRow>\n reopen(id: number): Promise<VacancyRow>\n remove(id: number): Promise<void>\n restore(id: number): Promise<VacancyRow>\n /** The order on screen, written as a whole. There is one order and only one. */\n reorder(ids: number[]): Promise<void>\n /** The publications, newest first. */\n versions(id: number): Promise<VacancyVersion[]>\n /** An old publication becomes the draft; putting it on the site is a separate step. */\n restoreVersion(id: number, number: number): Promise<VacancyDetail>\n}\n\n/** The path of the list under the panel's API. */\nexport const VACANCIES_API = 'vacancies'\n\n/**\n * Everything under `/vacancies`, below the panel's API path (§4.11) — except the categories, which\n * are the panel's shared category API under `vacancies/categories`.\n */\nexport function createVacanciesApi(admin: AdminContext): VacanciesApi {\n const base = `${admin.apiPath}/${VACANCIES_API}`\n const data = <T>(body: { data: T }): T => body.data\n const row = (id: number, action: string) =>\n admin.http.post<{ data: VacancyRow }>(`${base}/${id}/${action}`, {}).then(data)\n\n return {\n list: (query = {}) => {\n const search = new URLSearchParams()\n\n // The bin holds whatever was deleted, open or closed: `state` does not apply there, and\n // saying it would read as though it did.\n if (query.trashed) search.set('trashed', '1')\n // Always said otherwise: the server's default is the open ones too, but a list that asks\n // for what it shows does not depend on somebody else's default staying put.\n else search.set('state', query.state ?? 'open')\n\n // Only what was asked for: `category=` would ask for the vacancies of no category.\n if (query.q) search.set('q', query.q)\n if (query.category != null) search.set('category', String(query.category))\n if (query.status) search.set('status', query.status)\n\n return admin.http.get<VacanciesList>(`${base}?${search}`)\n },\n get: (id) => admin.http.get<{ data: VacancyDetail }>(`${base}/${id}`).then(data),\n create: (input) => admin.http.post<{ data: VacancyDetail }>(base, input).then(data),\n save: (id, input) => admin.http.put<{ data: VacancyDetail }>(`${base}/${id}`, input).then(data),\n discard: (id) =>\n admin.http.post<{ data: VacancyDetail }>(`${base}/${id}/discard`, {}).then(data),\n duplicate: (id) =>\n admin.http.post<{ data: VacancyDetail }>(`${base}/${id}/duplicate`, {}).then(data),\n publish: (id) => row(id, 'publish'),\n unpublish: (id) => row(id, 'unpublish'),\n close: (id) => row(id, 'close'),\n reopen: (id) => row(id, 'reopen'),\n remove: (id) => admin.http.delete<void>(`${base}/${id}`).then(() => undefined),\n restore: (id) => row(id, 'restore'),\n // No `category`, ever: vacancies have one order (§4.11), unlike services.\n reorder: (ids) => reorderItems(admin, VACANCIES_API, ids, null),\n versions: (id) =>\n admin.http.get<{ data: VacancyVersion[] }>(`${base}/${id}/versions`).then(data),\n restoreVersion: (id, number) =>\n admin.http\n .post<{ data: VacancyDetail }>(`${base}/${id}/versions/${number}/restore`, {})\n .then(data),\n }\n}\n","/**\n * A vacancy's dates are calendar days (decision 13): `valid_through` is the last day it is open,\n * `posted_at` the day it was first put up, both `YYYY-MM-DD` with no time and no zone.\n *\n * Nothing here may turn such a day into a moment. `new Date('2026-11-30')` is midnight *UTC*, and\n * west of Greenwich that is still the 29th — the column would print the day before the one the\n * editor picked, and the site (which reads the day as written) would disagree with the panel. So\n * the day is built from its parts, at local midnight, and formatted from there: the same three\n * numbers in every zone.\n *\n * The pickers of `vacancies.form` do the same by themselves: `valueFormat: \"yyyy-MM-dd\"` parses\n * and formats in the browser's own zone, so a day goes in and comes out unchanged.\n */\n\nconst DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\n/** The day as a local date, or `null` for anything that is not a `YYYY-MM-DD`. */\nexport function dayOf(value: string | null | undefined): Date | null {\n const parts = typeof value === 'string' ? DAY.exec(value.slice(0, 10)) : null\n\n if (parts === null) return null\n\n const [, year, month, day] = parts\n const at = new Date(Number(year), Number(month) - 1, Number(day))\n\n return Number.isNaN(at.getTime()) ? null : at\n}\n\n/**\n * The day in the panel's language — \"30 Nov\", and with the year when it is not this one. Empty\n * for no day at all.\n */\nexport function formatDay(\n value: string | null | undefined,\n locale: string,\n now = new Date(),\n): string {\n const at = dayOf(value)\n\n if (at === null) return ''\n\n return new Intl.DateTimeFormat(locale, {\n day: 'numeric',\n month: 'short',\n ...(at.getFullYear() === now.getFullYear() ? {} : { year: 'numeric' }),\n }).format(at)\n}\n\n/** The same day, in full — for a tip and for the head of the editor. */\nexport function longDay(value: string | null | undefined, locale: string): string {\n const at = dayOf(value)\n\n return at === null\n ? ''\n : new Intl.DateTimeFormat(locale, { day: 'numeric', month: 'long', year: 'numeric' }).format(at)\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * The English this package speaks on its own, before the server's dictionary arrives, or without\n * one. The server ships the same lines in ten languages under `webx-vacancies::` and overrides\n * these; `messages.test.ts` keeps the two sets of keys equal.\n *\n * `module`, `panel`, `editor` and `category` are the panel's own and match the server's files key\n * for key. `vacancy` is the site's group — the words of the page of a vacancy — and the panel\n * borrows the handful it prints in a row: where, the kind of employment. Only those are here.\n */\nexport const vacanciesMessages: Record<string, Messages> = {\n module: {\n group: 'Vacancies',\n vacancies: 'Vacancies',\n categories: 'Categories',\n },\n panel: {\n new: 'New vacancy',\n 'new-title': 'New vacancy',\n 'field-title': 'Position',\n create: 'Create',\n cancel: 'Cancel',\n search: 'Search by position or address',\n empty: 'No vacancies yet.',\n 'empty-help':\n 'A vacancy is who you are looking for: the position, where, the terms and the pay.',\n 'empty-open': 'No open vacancies. The closed ones are on the next tab.',\n 'empty-closed': 'No vacancy is closed.',\n 'empty-search': 'Nothing matches that.',\n 'empty-bin': 'The bin is empty.',\n\n 'status-draft': 'Draft',\n 'status-published': 'Live',\n 'status-modified': 'Live',\n 'status-unpublished': 'Off the site',\n edits: 'edits',\n 'closed-manual': 'Closed',\n 'closed-expired': 'Expired',\n 'closed-manual-help': 'The hiring was closed by hand.',\n 'closed-expired-help': 'Its last day is behind us.',\n 'no-address': 'No address in this language',\n 'no-city': 'No city',\n until: 'until :date',\n 'until-help': 'Open to the end of :date',\n\n 'view-open': 'Open',\n 'view-closed': 'Closed',\n 'view-all': 'All',\n bin: 'Bin',\n\n filters: 'Filters',\n 'filters-reset': 'Reset the filters',\n 'filter-status': 'State',\n 'any-status': 'Any state',\n 'filter-category': 'Category',\n 'any-category': 'Any category',\n\n 'order-all': 'Drag a vacancy by its handle to change the order the site lists them in.',\n 'order-closed':\n 'The order is set on the Open and All tabs: a closed vacancy is on no list of the site.',\n 'order-locked':\n 'The order can only be changed in the whole list — clear the search and the filters.',\n 'reorder-failed': 'The new order was not saved.',\n\n open: 'Open',\n 'open-on-site': 'Open on the site',\n 'copy-address': 'Copy the address',\n 'address-copied': 'The address is copied.',\n duplicate: 'Duplicate',\n duplicated: 'This is the copy: a draft. Change what differs and publish it.',\n close: 'Close the hiring',\n reopen: 'Reopen the hiring',\n 'close-title': 'Close “:title”?',\n 'close-text':\n 'It leaves every list of the site. Its page stays, marked as closed, so links from job boards still lead somewhere.',\n 'reopen-title': 'Reopen “:title”?',\n 'reopen-text': 'It is back on the lists of the site, unless its last day is already behind us.',\n 'reopen-expired':\n 'Its last day is behind us, so it stays closed: change “Open until” in the editor to reopen it.',\n closed: 'The hiring is closed.',\n reopened: 'The hiring is open again.',\n 'close-edits':\n 'This vacancy has edits waiting. Publish or discard them first — closing would put them on the site too.',\n publish: 'Publish',\n unpublish: 'Take off the site',\n delete: 'Delete',\n restore: 'Restore',\n 'delete-title': 'Delete “:title”?',\n 'delete-text': 'It goes to the bin and comes off the site, and its address is free again.',\n deleted: 'The vacancy is in the bin.',\n restored: 'The vacancy is back.',\n published: 'The vacancy is on the site.',\n 'unpublished-done': 'The vacancy is off the site.',\n },\n editor: {\n trail: 'Where this vacancy sits',\n untitled: 'Untitled',\n save: 'Save',\n 'save-failed': 'The vacancy was not saved.',\n 'publish-title': 'Put “:title” on the site?',\n 'publish-text':\n 'It answers at :address from the moment you do, for everyone — with its categories and form as they are chosen now.',\n 'publish-nowhere': 'It has no address in this language yet, so nothing will answer.',\n preview: 'Preview',\n discard: 'Discard changes',\n 'discard-title': 'Discard what is waiting?',\n 'discard-text':\n 'The vacancy goes back to what the site is showing. What was written since is not listed anywhere.',\n discarded: 'The vacancy is back to what is published.',\n 'conflict-title': 'The vacancy changed while you were editing',\n 'conflict-mine': 'Keep mine',\n 'conflict-theirs': 'Take the newer version',\n 'conflict-theirs-title': 'Give up what you wrote?',\n 'conflict-theirs-text':\n 'The vacancy is read again as it now is, and what you have typed since goes.',\n leave: 'Leave',\n 'leave-title': 'Leave without saving?',\n 'leave-text': 'The vacancy could not be saved, and what you wrote is not on the server.',\n 'live-since': 'On the site since :date',\n 'open-until': 'Open until :date',\n 'address-moving':\n 'The address is changing. The old one keeps working and leads to the new one.',\n 'history-empty': 'This vacancy has never been published.',\n version: '#:number',\n 'version-live': 'On the site',\n 'source-panel': 'From the panel',\n 'source-mcp': 'By an agent',\n 'source-import': 'Imported',\n 'restore-title': 'Restore version :number?',\n 'restore-text':\n 'It becomes the draft. The site keeps showing what is published until you publish this.',\n 'restore-version': 'Restore',\n 'restored-version': 'Version :number is now the draft.',\n },\n category: {\n new: 'New category',\n empty: 'No categories yet.',\n 'empty-help':\n 'A category is a group of vacancies — development, sales — and a filter of the list on the site. It has no page of its own.',\n order: 'Drag to change the order of the groups on the site.',\n hidden: 'Hidden from the site',\n 'no-page': 'A group, with no page of its own',\n vacancies: 'Vacancies: :count',\n 'show-vacancies': 'Show its vacancies',\n 'delete-blocked': 'This category still holds vacancies. Move them first.',\n 'delete-text': 'It goes to the bin. Its vacancies stay.',\n deleted: 'The category is in the bin.',\n saved: 'The category is saved.',\n 'field-title': 'Title',\n 'field-slug': 'Key',\n 'field-slug-help':\n 'In the address of the filter: ?category=… Latin letters, digits and hyphens.',\n },\n vacancy: {\n workplace: {\n onsite: 'On site',\n remote: 'Remote',\n hybrid: 'Hybrid',\n },\n employment: {\n FULL_TIME: 'Full-time',\n PART_TIME: 'Part-time',\n CONTRACTOR: 'Contract',\n TEMPORARY: 'Temporary',\n INTERN: 'Internship',\n VOLUNTEER: 'Volunteer',\n PER_DIEM: 'Per day',\n OTHER: 'Other',\n },\n },\n}\n\n/** The word of a schema.org employment type: `FULL_TIME` → `vacancy.employment.FULL_TIME`, as the server names it. */\nexport function employmentKey(code: string): string {\n return `vacancy.employment.${code}`\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { vacanciesMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useVacanciesMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-vacancies', vacanciesMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, useTemplateRef } from 'vue'\nimport { useAdmin, useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxFormItem, WxInput, WxSpace } from '@webx-ui/core'\nimport { createVacanciesApi } from './api'\nimport { useVacanciesMessages } from './i18n'\nimport type { VacancyRow } from './types'\n\n/**\n * A new vacancy: what it is called, and nothing else. The server makes the address out of the\n * title; the address field on the settings tab is where there is room to say what changing it\n * costs.\n */\nconst { open, resolve, dismiss } = useModal<VacancyRow>()\n\nconst context = useAdmin()\nconst api = createVacanciesApi(context)\nuseVacanciesMessages()\n\nconst t = useTranslate('webx-vacancies')\n\nconst title = ref('')\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst field = useTemplateRef<HTMLElement>('field')\n\nonMounted(async () => {\n await nextTick()\n field.value?.querySelector('input')?.focus()\n})\n\nasync function submit(): Promise<void> {\n if (title.value.trim() === '' || saving.value) return\n\n saving.value = true\n errors.value = {}\n\n try {\n resolve((await api.create({ title: title.value.trim() })).vacancy)\n } catch (error) {\n // The address is what can be refused here, and it is refused under the title that made it:\n // the registry sees the categories and the pages too, so \"taken\" can mean taken by either.\n errors.value = (error as { body?: { errors?: Record<string, string[]> } }).body?.errors ?? {}\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('panel.new-title')\" :width=\"440\">\n <wx-form-item\n :label=\"t('panel.field-title')\"\n :error=\"errors.title?.[0] ?? errors.slug?.[0]\"\n required\n >\n <div ref=\"field\">\n <wx-input v-model=\"title\" :aria-label=\"t('panel.field-title')\" @keyup.enter=\"submit\" />\n </div>\n </wx-form-item>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('panel.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" :disabled=\"title.trim() === ''\" @click=\"submit\">\n {{ t('panel.create') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { useRoute, useRouter, type LocationQueryRaw } from 'vue-router'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxFilterChips,\n WxListScreen,\n WxRowMenu,\n type AppliedFilter,\n type RowAction,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n createModal,\n toast,\n WxAction,\n WxAlert,\n WxBadge,\n WxButton,\n WxEmpty,\n WxFormItem,\n WxIcon,\n WxIndicator,\n WxInput,\n WxPopover,\n WxSelect,\n WxSkeleton,\n WxSortableList,\n WxTooltip,\n type TabItem,\n type TabValue,\n} from '@webx-ui/core'\nimport { createVacanciesApi } from './api'\nimport { formatDay, longDay } from './days'\nimport { useVacanciesMessages } from './i18n'\nimport { employmentKey } from './messages'\nimport VacancyCreateDialog from './VacancyCreateDialog.vue'\nimport type { VacanciesList, VacancyRow, VacancyState, VacancyStatus } from './types'\n\n/**\n * Every vacancy at once, in the order the site lists them (§4.10).\n *\n * The shape of the recipes, with the tabs of the events: no paginator, because this is where the\n * order is dragged and a drag cannot cross a page — a site has tens of vacancies, not thousands.\n * The tabs are *open or closed*, not the state: the open ones are what the site lists and what an\n * editor comes here for, so they are the list with nothing chosen. \"All\" draws the closed ones\n * muted, so the line between the two can be seen at a glance; the category, the state and the\n * search narrow any of them.\n *\n * One order, and only one (§4.11). The grips are there on \"Open\" and \"All\" while nothing narrows\n * the list — a selection has invisible gaps in it, and a closed vacancy is on no list of the site,\n * so ordering the \"Closed\" tab would be ordering nothing. The line under the rows says which of\n * the three it is.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/vacancies' })\n\nconst context = useAdmin()\nconst api = createVacanciesApi(context)\nconst route = useRoute()\nconst router = useRouter()\nuseVacanciesMessages()\n\nconst t = useTranslate('webx-vacancies')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst rows = ref<VacancyRow[]>([])\nconst filters = ref<VacanciesList['filters']>({ categories: [] })\nconst loading = ref(true)\n\nconst create = createModal<VacancyRow, Record<string, never>>(VacancyCreateDialog)\n\nconst canManage = computed(() => context.can('vacancies.manage'))\n\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'vacancies')?.title ??\n t('module.vacancies'),\n)\n\nconst locale = computed(() => context.i18n.state.locale)\n\n/*\n * Everything the list is looking at lives in the address, so coming back from a vacancy lands on\n * the same tab, the same filters and the same search.\n */\nconst view = computed<TabValue>({\n get: () => (typeof route.query.view === 'string' ? route.query.view : ''),\n set: (value) => {\n void router.replace({\n query: { ...route.query, view: value === '' ? undefined : String(value) },\n })\n },\n})\n\nconst inBin = computed(() => view.value === 'trashed')\n\nconst state = computed<VacancyState>(() => {\n if (view.value === 'closed') return 'closed'\n\n return view.value === 'all' ? 'all' : 'open'\n})\n\nconst category = computed(() => {\n const raw = route.query.category\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) || null : null\n})\n\nconst status = computed(\n () => (typeof route.query.status === 'string' ? route.query.status : '') as VacancyStatus | '',\n)\n\nconst q = computed(() => (typeof route.query.q === 'string' ? route.query.q : ''))\n\n/* What is typed, ahead of the address: the address follows it after a pause. */\nconst typed = ref(q.value)\n\nconst views = computed<TabItem[]>(() => [\n { value: '', label: t('panel.view-open') },\n { value: 'closed', label: t('panel.view-closed') },\n { value: 'all', label: t('panel.view-all') },\n { value: 'trashed', label: t('panel.bin'), icon: 'trash' },\n])\n\nconst statuses = computed(() =>\n (['published', 'draft', 'unpublished'] as const).map((one) => ({\n value: one,\n label: t(`panel.status-${one}`),\n })),\n)\n\n/** Nothing narrows the list: no search, no category, no state. */\nconst unfiltered = computed(\n () => q.value.trim() === '' && category.value === null && status.value === '',\n)\n\n/** The one order, as far as this tab can show it (§4.10). */\nconst ordered = computed(() => state.value !== 'closed' && !inBin.value)\n\n/* Dragged only by somebody who may write, and only in a list that is the whole order. */\nconst sortable = computed(() => canManage.value && ordered.value && unfiltered.value)\n\nconst hint = computed(() => {\n if (!ordered.value) return t('panel.order-closed')\n\n return t(unfiltered.value ? 'panel.order-all' : 'panel.order-locked')\n})\n\nconst emptyText = computed(() => {\n if (q.value !== '') return t('panel.empty-search')\n if (inBin.value) return t('panel.empty-bin')\n if (state.value === 'closed') return t('panel.empty-closed')\n\n return state.value === 'all' ? t('panel.empty') : t('panel.empty-open')\n})\n\n/**\n * What the dropdowns behind the funnel are set to, said in the reader's words. A shut panel says\n * nothing about itself, and a list narrowed by something nobody can see is a list that looks\n * wrong — here doubly, because it is also why the grips went away.\n */\nconst applied = computed<AppliedFilter[]>(() => {\n const chips: AppliedFilter[] = []\n\n if (status.value !== '') {\n chips.push({\n key: 'status',\n label: `${t('panel.filter-status')}: ${t(`panel.status-${status.value}`)}`,\n clear: () => narrow('status', undefined),\n })\n }\n\n const chosen = filters.value.categories.find((item) => item.id === category.value)\n\n if (chosen) {\n chips.push({\n key: 'category',\n label: `${t('panel.filter-category')}: ${chosen.title}`,\n clear: () => narrow('category', undefined),\n })\n }\n\n return chips\n})\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const answer = await api.list({\n state: state.value,\n q: q.value,\n category: category.value,\n status: inBin.value ? '' : status.value,\n trashed: inBin.value,\n })\n\n rows.value = answer.data\n filters.value = answer.filters\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n}\n\n/*\n * Sources one by one, not a getter returning an array: a fresh array is a new value on every\n * change of the address, and the list would be read twice for one click.\n */\nwatch([view, category, status, q], () => void load())\nvoid load()\n\nlet pause: ReturnType<typeof setTimeout> | undefined\n\nwatch(typed, (value) => {\n clearTimeout(pause)\n pause = setTimeout(() => {\n void router.replace({ query: { ...route.query, q: value.trim() === '' ? undefined : value } })\n }, 300)\n})\n\nonBeforeUnmount(() => clearTimeout(pause))\n\nfunction narrow(name: 'category' | 'status', value: unknown): void {\n void router.replace({\n query: {\n ...route.query,\n [name]:\n typeof value === 'number' || (typeof value === 'string' && value !== '')\n ? String(value)\n : undefined,\n },\n })\n}\n\nfunction clearFilters(): void {\n const query: LocationQueryRaw = { ...route.query }\n\n delete query.category\n delete query.status\n void router.replace({ query })\n}\n\nfunction open(vacancy: VacancyRow): void {\n void router.push({ path: `${props.base}/${vacancy.id}`, query: { ...route.query } })\n}\n\n/** A new vacancy: the dialog asks for the position, the editor opens. */\nasync function add(): Promise<void> {\n const made = await create({})\n\n if (made) open(made)\n}\n\n/**\n * The order on screen, written as a whole. The list is already in its new order\n * (`WxSortableList` reorders the model before it says so), so a failure has to put it back\n * rather than leave the screen disagreeing with the database.\n */\nasync function reorder(): Promise<void> {\n try {\n await api.reorder(rows.value.map((row) => row.id))\n } catch (error) {\n toast.danger(message(error, t('panel.reorder-failed')))\n await load()\n }\n}\n\nconst live = (vacancy: VacancyRow) =>\n vacancy.status === 'published' || vacancy.status === 'modified'\n\nfunction actionsFor(vacancy: VacancyRow): RowAction[] {\n if (!canManage.value) return []\n\n if (inBin.value) {\n return [\n {\n key: 'restore',\n icon: 'refresh',\n label: t('panel.restore'),\n run: () => void restore(vacancy),\n },\n ]\n }\n\n const actions: RowAction[] = [\n { key: 'open', icon: 'edit', label: t('panel.open'), run: () => open(vacancy) },\n {\n key: 'duplicate',\n icon: 'copy',\n label: t('panel.duplicate'),\n run: () => void duplicate(vacancy),\n },\n ]\n\n if (vacancy.url && vacancy.status !== 'draft') {\n actions.push({\n key: 'site',\n icon: 'external-link',\n label: t('panel.open-on-site'),\n href: vacancy.url,\n })\n }\n\n /*\n * Closing is a publication of `is_closed` (§4.11), so it is offered only for what is on the\n * site: closing a draft would publish it. A vacancy closed only by its date is not reopened\n * here — its date is what closes it, and that is changed in the editor.\n */\n if (live(vacancy) && vacancy.closed_reason === 'manual') {\n actions.push({\n key: 'reopen',\n icon: 'refresh',\n label: t('panel.reopen'),\n run: () => void reopen(vacancy),\n })\n } else if (live(vacancy) && vacancy.closed_reason === null) {\n actions.push({\n key: 'close',\n icon: 'lock',\n label: t('panel.close'),\n run: () => void close(vacancy),\n })\n }\n\n actions.push(\n live(vacancy)\n ? {\n key: 'unpublish',\n icon: 'eye-off',\n label: t('panel.unpublish'),\n run: () => void run(() => api.unpublish(vacancy.id), t('panel.unpublished-done')),\n }\n : {\n key: 'publish',\n icon: 'upload',\n label: t('panel.publish'),\n run: () => void run(() => api.publish(vacancy.id), t('panel.published')),\n },\n )\n\n actions.push({\n key: 'delete',\n icon: 'trash',\n label: t('panel.delete'),\n danger: true,\n run: () => void remove(vacancy),\n })\n\n return actions\n}\n\n/**\n * The same position somewhere else (decision 20): a copy as a draft, opened straight away — the\n * city is the first thing that differs, and it is on the editor, not here.\n */\nasync function duplicate(vacancy: VacancyRow): Promise<void> {\n try {\n const copy = await api.duplicate(vacancy.id)\n\n toast.success(t('panel.duplicated'))\n open(copy.vacancy)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/**\n * Closing publishes, so edits waiting in the draft would go out with it; the server refuses that\n * with a 409, and the panel says so before asking — the answer is the same, and a question whose\n * only answer is \"no\" is not one to put.\n */\nasync function close(vacancy: VacancyRow): Promise<void> {\n if (vacancy.status === 'modified') {\n toast.warning(t('panel.close-edits'))\n\n return\n }\n\n const agreed = await confirm({\n title: t('panel.close-title', { title: vacancy.title }),\n message: t('panel.close-text'),\n confirmText: t('panel.close'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n await run(() => api.close(vacancy.id), t('panel.closed'))\n}\n\nasync function reopen(vacancy: VacancyRow): Promise<void> {\n if (vacancy.status === 'modified') {\n toast.warning(t('panel.close-edits'))\n\n return\n }\n\n const agreed = await confirm({\n title: t('panel.reopen-title', { title: vacancy.title }),\n message: t('panel.reopen-text'),\n confirmText: t('panel.reopen'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n try {\n const row = await api.reopen(vacancy.id)\n\n // Taken off by hand and past its day as well: it is open to the switch, closed to the date.\n if (row.closed) toast.warning(t('panel.reopen-expired'))\n else toast.success(t('panel.reopened'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\nasync function remove(vacancy: VacancyRow): Promise<void> {\n const agreed = await confirm({\n title: t('panel.delete-title', { title: vacancy.title }),\n message: t('panel.delete-text'),\n confirmText: t('panel.delete'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await run(() => api.remove(vacancy.id), t('panel.deleted'))\n}\n\nasync function restore(vacancy: VacancyRow): Promise<void> {\n await run(() => api.restore(vacancy.id), t('panel.restored'))\n}\n\n/** One request, one sentence about it, and the list as the server now sees it. */\nasync function run(request: () => Promise<unknown>, said: string): Promise<void> {\n try {\n await request()\n toast.success(said)\n } catch (error) {\n toast.danger(message(error))\n } finally {\n await load()\n }\n}\n\n/* Live and live-with-edits are the same green; what is waiting has a chip of its own. */\nfunction badge(value: VacancyStatus): 'default' | 'success' {\n return value === 'published' || value === 'modified' ? 'success' : 'default'\n}\n\nfunction address(vacancy: VacancyRow): string {\n if (inBin.value) return '—'\n\n return vacancy.path === null ? t('panel.no-address') : `/${vacancy.path}`\n}\n\n/** Where: the city, \"Remote\", or the city and \"Hybrid\" — what a candidate reads first. */\nfunction where(vacancy: VacancyRow): string {\n if (vacancy.workplace === 'remote') return t('vacancy.workplace.remote')\n\n const city = vacancy.city !== '' ? vacancy.city : t('panel.no-city')\n\n return vacancy.workplace === 'hybrid' ? `${city} · ${t('vacancy.workplace.hybrid')}` : city\n}\n\nfunction employment(vacancy: VacancyRow): string {\n return vacancy.employment_types.map((code) => t(employmentKey(code))).join(', ')\n}\n\n/* In \"all\" the closed ones are muted, so the line between the two halves shows. */\nfunction muted(vacancy: VacancyRow): boolean {\n return vacancy.closed && state.value === 'all' && !inBin.value\n}\n\n/* What the section offers. Declared, because on a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value\n ? [{ key: 'new', label: t('panel.new'), icon: 'plus', primary: true, run: () => void add() }]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-vacancies\">\n <wx-list-screen\n v-model:view=\"view\"\n :title=\"title\"\n :views=\"views\"\n :actions=\"actions\"\n padding=\"sm\"\n >\n <div class=\"wx-vacancies__bar\">\n <wx-input\n v-model=\"typed\"\n class=\"wx-vacancies__search\"\n size=\"sm\"\n clearable\n :placeholder=\"t('panel.search')\"\n :aria-label=\"t('panel.search')\"\n >\n <template #prefix><wx-icon name=\"search\" size=\"sm\" /></template>\n </wx-input>\n\n <!--\n The funnel, the way `WxTable` draws its own: the panel hangs off the indicator rather\n than off the button, because `WxAction` carries its tooltip beside itself and a trigger\n has to be one element (CLAUDE.md §4).\n -->\n <wx-popover :title=\"t('panel.filters')\" :width=\"280\" side=\"bottom\" align=\"end\">\n <template #trigger>\n <wx-indicator\n class=\"wx-vacancies__funnel\"\n :value=\"applied.length\"\n :hidden=\"applied.length === 0\"\n type=\"primary\"\n :label=\"t('panel.filters')\"\n >\n <wx-action icon=\"filter\" size=\"sm\" :title=\"t('panel.filters')\" />\n </wx-indicator>\n </template>\n\n <div class=\"wx-vacancies__filters\">\n <wx-form-item v-if=\"!inBin\" :label=\"t('panel.filter-status')\">\n <wx-select\n :model-value=\"status === '' ? null : status\"\n :options=\"statuses\"\n :placeholder=\"t('panel.any-status')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('status', value)\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('panel.filter-category')\">\n <wx-select\n :model-value=\"category\"\n :options=\"filters.categories.map((item) => ({ value: item.id, label: item.title }))\"\n :placeholder=\"t('panel.any-category')\"\n clearable\n size=\"sm\"\n @update:model-value=\"(value: unknown) => narrow('category', value)\"\n />\n </wx-form-item>\n\n <wx-button\n v-if=\"applied.length > 0\"\n variant=\"text\"\n size=\"sm\"\n block\n @click=\"clearFilters\"\n >\n <template #icon><wx-icon name=\"close\" /></template>\n {{ t('panel.filters-reset') }}\n </wx-button>\n </div>\n </wx-popover>\n </div>\n\n <div v-if=\"applied.length > 0\" class=\"wx-vacancies__applied\">\n <wx-filter-chips :filters=\"applied\" />\n </div>\n\n <wx-skeleton v-if=\"loading && rows.length === 0\" class=\"wx-vacancies__loading\" :rows=\"5\" />\n\n <wx-empty\n v-else-if=\"rows.length === 0\"\n :title=\"emptyText\"\n :description=\"state === 'all' && !inBin && unfiltered ? t('panel.empty-help') : undefined\"\n />\n\n <template v-else>\n <!--\n A grip and not the whole row: the row opens a vacancy, and a row that both opens and\n drags is one where one of the two happens by accident. No grips at all where the list\n is a selection — a handle that moves rows past invisible ones would be lying.\n -->\n <wx-sortable-list\n v-model=\"rows\"\n class=\"wx-vacancies__list\"\n :class=\"{ 'is-loading': loading }\"\n plain\n item-key=\"id\"\n :handle=\"sortable ? 'grip' : 'row'\"\n :item-label=\"(item: VacancyRow) => item.title\"\n :disabled=\"!sortable\"\n @move=\"reorder\"\n >\n <template #default=\"{ item }\">\n <component\n :is=\"inBin ? 'div' : 'router-link'\"\n class=\"wx-vacancy-row\"\n :class=\"{ 'is-closed': muted(item) }\"\n v-bind=\"inBin ? {} : { to: { path: `${props.base}/${item.id}`, query: route.query } }\"\n >\n <span class=\"wx-vacancy-row__name\">\n <span class=\"wx-vacancy-row__title\">{{ item.title }}</span>\n <span class=\"wx-vacancy-row__address\">{{ address(item) }}</span>\n </span>\n\n <span class=\"wx-vacancy-row__facts\">\n <span class=\"wx-vacancy-row__where\">\n <wx-icon :name=\"item.workplace === 'remote' ? 'monitor' : 'map-pin'\" size=\"sm\" />\n {{ where(item) }}\n </span>\n <span v-if=\"item.employment_types.length > 0\" class=\"wx-vacancy-row__employment\">\n {{ employment(item) }}\n </span>\n <wx-tooltip\n v-if=\"item.valid_through\"\n :content=\"t('panel.until-help', { date: longDay(item.valid_through, locale) })\"\n >\n <span class=\"wx-vacancy-row__until\">\n <wx-icon name=\"calendar\" size=\"sm\" />\n {{ t('panel.until', { date: formatDay(item.valid_through, locale) }) }}\n </span>\n </wx-tooltip>\n </span>\n\n <span class=\"wx-vacancy-row__state\">\n <wx-badge v-for=\"chip in item.categories\" :key=\"chip.id\" size=\"sm\" round>\n {{ chip.title }}\n </wx-badge>\n <wx-tooltip\n v-if=\"item.closed_reason\"\n :content=\"t(`panel.closed-${item.closed_reason}-help`)\"\n >\n <wx-badge type=\"warning\" size=\"sm\" round>\n {{ t(`panel.closed-${item.closed_reason}`) }}\n </wx-badge>\n </wx-tooltip>\n <wx-badge :type=\"badge(item.status)\" dot size=\"sm\">\n {{ t(`panel.status-${item.status}`) }}\n </wx-badge>\n <wx-badge v-if=\"item.status === 'modified'\" type=\"primary\" size=\"sm\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n </span>\n </component>\n </template>\n\n <template #actions=\"{ item }\">\n <wx-row-menu :actions=\"actionsFor(item)\" :label=\"item.title\" />\n </template>\n </wx-sortable-list>\n\n <!-- What a drag does here, or why there is nothing to drag — under the rows it is about. -->\n <wx-alert\n v-if=\"canManage && !inBin\"\n class=\"wx-vacancies__note\"\n type=\"info\"\n :description=\"hint\"\n />\n </template>\n </wx-list-screen>\n </div>\n</template>\n\n<style scoped>\n.wx-vacancies {\n min-width: 0;\n container-type: inline-size;\n}\n\n.wx-vacancies__bar {\n display: flex;\n align-items: center;\n gap: var(--wx-space-8);\n padding: var(--wx-space-4) var(--wx-space-8) var(--wx-space-8);\n}\n\n.wx-vacancies__search {\n flex: 1 1 240px;\n min-width: 0;\n max-width: 420px;\n}\n\n.wx-vacancies__funnel {\n flex: none;\n}\n\n.wx-vacancies__filters {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-4);\n}\n\n.wx-vacancies__applied {\n padding: 0 var(--wx-space-8) var(--wx-space-8);\n}\n\n.wx-vacancies__loading {\n padding: var(--wx-space-8);\n}\n\n.wx-vacancies__list.is-loading {\n opacity: 0.6;\n}\n\n.wx-vacancies__list :deep(.wx-sortable-list__row) {\n padding-inline: var(--wx-space-8);\n}\n\n.wx-vacancies__list :deep(.wx-sortable-list__row:hover) {\n background: var(--wx-bg-subtle);\n border-radius: var(--wx-radius-sm);\n}\n\n.wx-vacancies__note {\n margin-block-start: var(--wx-space-8);\n}\n\n/*\n * Three columns on a wide list: who, the facts a candidate reads, the state. The facts are\n * the ones that fold first — under the name on a narrow list.\n */\n.wx-vacancy-row {\n display: grid;\n /* Each row is a grid of its own, so the state column has a width rather than `auto`: sized by\n its badges, it would start the facts at a different place on every row. */\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 260px;\n grid-template-areas: 'name facts state';\n align-items: center;\n gap: var(--wx-space-4) var(--wx-space-16);\n flex: 1 1 auto;\n min-width: 0;\n padding-block: var(--wx-space-6);\n color: inherit;\n text-decoration: none;\n}\n\n.wx-vacancy-row__name {\n grid-area: name;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.wx-vacancy-row__title {\n overflow: hidden;\n font-weight: var(--wx-font-weight-medium);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-vacancy-row__address {\n overflow: hidden;\n color: var(--wx-text-muted);\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-xs);\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.wx-vacancy-row__facts {\n grid-area: facts;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--wx-space-2) var(--wx-space-12);\n min-width: 0;\n color: var(--wx-text-muted);\n font-size: var(--wx-font-size-sm);\n}\n\n.wx-vacancy-row__where,\n.wx-vacancy-row__until {\n display: inline-flex;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n white-space: nowrap;\n}\n\n.wx-vacancy-row__employment {\n min-width: 0;\n}\n\n.wx-vacancy-row__state {\n grid-area: state;\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n align-items: center;\n gap: var(--wx-space-4);\n min-width: 0;\n}\n\n/*\n * A closed vacancy in \"all\": the whole row a step quieter rather than a badge alone — \"is this\n * still open\" is answered by the eye running down the list, not by reading.\n */\n.wx-vacancy-row.is-closed .wx-vacancy-row__title,\n.wx-vacancy-row.is-closed .wx-vacancy-row__facts {\n color: var(--wx-text-muted);\n}\n\n/*\n * Narrow: the facts and the state go under the name rather than beside it, so the name keeps the\n * width. By the width of the list, not of the window — the panel's sidebar decides how much the\n * list has.\n */\n@container (max-width: 720px) {\n .wx-vacancy-row {\n grid-template-columns: minmax(0, 1fr);\n grid-template-areas:\n 'name'\n 'facts'\n 'state';\n align-items: start;\n }\n\n .wx-vacancy-row__state {\n justify-content: flex-start;\n }\n\n .wx-vacancies__list :deep(.wx-sortable-list__row) {\n gap: var(--wx-space-8);\n padding-inline: var(--wx-space-4);\n }\n\n .wx-vacancies__bar,\n .wx-vacancies__applied {\n padding-inline: var(--wx-space-4);\n }\n\n .wx-vacancies__search {\n max-width: none;\n }\n}\n</style>\n","import { inject, provide, type InjectionKey, type Ref } from 'vue'\nimport type { VacancyRow } from './types'\n\n/**\n * What the editor knows and the nodes of `vacancies.form` do not: the vacancy being edited, and a\n * way to read it again. The history needs both — its list is keyed by the vacancy, and a restored\n * version has to reach the fields the form is bound to.\n */\nexport interface VacancyEditorContext {\n /** The vacancy as the server last answered it, or `null` while the first request is out. */\n vacancy: Ref<VacancyRow | null>\n /** Whether this administrator may write at all. */\n canManage: boolean\n /** Ask the server for the vacancy again — after a restore, a publication, a discard. */\n reload(): Promise<void>\n}\n\nexport const vacancyEditorKey: InjectionKey<VacancyEditorContext> = Symbol('wx-vacancy-editor')\n\nexport function provideVacancyEditor(editor: VacancyEditorContext): void {\n provide(vacancyEditorKey, editor)\n}\n\n/** The editor above this node, or `null` outside one — a demo or a test draws nothing. */\nexport function useVacancyEditor(): VacancyEditorContext | null {\n return inject(vacancyEditorKey, null)\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n provideRecordAddress,\n provideRelationOwner,\n useAdmin,\n useDates,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxAlert,\n WxBadge,\n WxBreadcrumb,\n WxBreadcrumbItem,\n WxButton,\n WxCard,\n WxIcon,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createVacanciesApi } from './api'\nimport { longDay } from './days'\nimport { provideVacancyEditor } from './editor'\nimport { useVacanciesMessages } from './i18n'\nimport type { VacancyConflict, VacancyDetail, VacancyRow } from './types'\n\n/**\n * The editor of one vacancy: a head that stays put, the described screen under it, and the bar\n * along the bottom where it is saved, duplicated and published (§4.10).\n *\n * Everything between the head and the bar is `vacancies.form` — the vacancy, its settings, the SEO\n * card `module-seo` patches on and the history — so a project adds its own field with a patch\n * rather than a fork of this file. Saving is by autosave into the draft, and the draft carries\n * the categories, the form and \"closed\" too: the site changes, all of it at once, only on\n * \"Publish\". Edits are guarded by a revision: a save over somebody else's is refused with a 409\n * and the question of which version the site gets.\n *\n * The two dates are calendar days, not moments (decision 13): the pickers hold `YYYY-MM-DD` as\n * written, and the head prints them from their parts (`days.ts`), so no zone moves them.\n */\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/vacancies' })\n\nconst context = useAdmin()\nconst api = createVacanciesApi(context)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nconst dates = useDates()\nuseVacanciesMessages()\n\nconst t = useTranslate('webx-vacancies')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** How long after the last keystroke the draft goes to the server. */\nconst PAUSE = 1200\n\nconst id = computed(() => Number(route.params.id))\nconst list = computed(() => props.base)\n\nconst vacancy = ref<VacancyRow | null>(null)\nconst values = ref<ScreenModel>({})\nconst revision = ref('')\nconst prefix = ref<string | null>(null)\nconst previewUrl = ref<string | null>(null)\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst working = ref(false)\nconst errors = ref<Record<string, string[]>>({})\nconst conflict = ref<VacancyConflict | null>(null)\n\nconst snapshot = ref('')\n\nconst canManage = computed(() => context.can('vacancies.manage'))\n\n/** Closed for writing: no permission, or a publication in flight. */\nconst locked = computed(() => !canManage.value || working.value)\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(() => snapshot.value !== '' && current.value !== snapshot.value)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value ? 'unsaved' : 'saved'\n})\n\n/** The name follows the field rather than the answer: a rename shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n\n return localizedValue(written, locales.active.value, vacancy.value?.title ?? '')\n})\n\n/**\n * Under the name: how long it is open, and since when it is on the site — the two facts the\n * badges beside the name cannot carry.\n */\nconst subtitle = computed(() => {\n const row = vacancy.value\n\n if (!row) return ''\n\n const parts: string[] = []\n\n if (row.valid_through !== null && !row.closed) {\n parts.push(\n t('editor.open-until', { date: longDay(row.valid_through, context.i18n.state.locale) }),\n )\n }\n\n if (row.status === 'published' || row.status === 'modified') {\n parts.push(t('editor.live-since', { date: dates.short(row.published_at) }))\n }\n\n return parts.join(' · ')\n})\n\n/* The vacancy as the owner of its relations — the one it has is the response form (decision 10). */\nprovideRelationOwner({ type: 'vacancy', id: computed(() => vacancy.value?.id ?? null) })\n\n/* The address field is the panel's shared one (`wx-slug`), and this is what it prints. */\nprovideRecordAddress({\n values,\n prefix,\n path: computed(() => vacancy.value?.path),\n moving: () => t('editor.address-moving'),\n})\n\nprovideVacancyEditor({ vacancy, canManage: canManage.value, reload: () => load(true) })\n\nfunction take(detail: VacancyDetail): void {\n const taken = detail.values\n\n vacancy.value = detail.vacancy\n values.value = taken\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n snapshot.value = JSON.stringify(taken)\n conflict.value = null\n}\n\n/**\n * Read the vacancy again — quietly unless this is the first time: the skeleton replaces the whole\n * screen, and a publication answered with it would throw the editor back onto the first tab.\n */\nasync function load(silent = false): Promise<void> {\n if (!silent) loading.value = true\n\n try {\n take(await api.get(id.value))\n } catch (error) {\n toast.danger(message(error))\n void router.push(list.value)\n } finally {\n loading.value = false\n }\n}\n\n/* One pending save at a time, and one pending pause. */\nlet timer: ReturnType<typeof setTimeout> | undefined\n\nfunction schedule(): void {\n if (!canManage.value || conflict.value || !dirty.value) return\n\n clearTimeout(timer)\n timer = setTimeout(() => void save(), PAUSE)\n}\n\n/** A field was left: write now rather than at the end of a pause nobody is waiting through. */\nfunction onFocusOut(): void {\n if (!canManage.value || conflict.value || !dirty.value || saving.value) return\n\n clearTimeout(timer)\n void save()\n}\n\nasync function save(): Promise<void> {\n if (!vacancy.value || saving.value || !canManage.value) return\n\n clearTimeout(timer)\n\n // What is being sent, so that whatever is typed while the request is in flight stays dirty and\n // gets its own save rather than being marked as written.\n const sending = current.value\n const sent = values.value\n\n saving.value = true\n errors.value = {}\n\n try {\n const detail = await api.save(vacancy.value.id, { values: sent, revision: revision.value })\n\n vacancy.value = detail.vacancy\n revision.value = detail.revision\n prefix.value = detail.prefix\n previewUrl.value = detail.preview_url\n conflict.value = null\n\n if (current.value === sending) snapshot.value = sending\n } catch (error) {\n const failure = error as {\n status?: number\n body?: VacancyConflict & { errors?: Record<string, string[]> }\n }\n\n if (failure.status === 409 && failure.body?.data) {\n conflict.value = failure.body\n } else if (failure.body?.errors) {\n errors.value = failure.body.errors\n toast.danger(t('editor.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\n/** Give up what was typed and take the vacancy as it now is. */\nasync function takeTheirs(): Promise<void> {\n const agreed = await confirm({\n title: t('editor.conflict-theirs-title'),\n message: t('editor.conflict-theirs-text'),\n confirmText: t('editor.conflict-theirs'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n await load(true)\n}\n\n/** Keep what was typed and write it over the other version — which is still in the history. */\nasync function keepMine(): Promise<void> {\n const theirs = conflict.value\n\n if (!theirs) return\n\n revision.value = theirs.data.revision\n conflict.value = null\n\n await save()\n}\n\n/** Throw away what is waiting — asked about, because it is the one button here that loses writing. */\nasync function discard(): Promise<void> {\n if (!vacancy.value) return\n\n const agreed = await confirm({\n title: t('editor.discard-title'),\n message: t('editor.discard-text'),\n confirmText: t('editor.discard'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n take(await api.discard(vacancy.value.id))\n toast.success(t('editor.discarded'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/** Publishing is asked about, because it is the one action here that visitors see. */\nasync function publish(): Promise<void> {\n const row = vacancy.value\n\n if (!row) return\n\n const agreed = await confirm({\n title: t('editor.publish-title', { title: title.value || row.title }),\n message:\n row.path === null\n ? t('editor.publish-nowhere')\n : t('editor.publish-text', { address: `/${row.path}` }),\n confirmText: t('panel.publish'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n if (dirty.value) await save()\n if (conflict.value) return\n\n working.value = true\n\n try {\n await api.publish(row.id)\n await load(true)\n toast.success(t('panel.published'))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n/**\n * The same position somewhere else: a copy as a draft, opened at once (decision 20). What is typed\n * here goes first — the copy is made from what the server holds, and an edit still in the pause\n * would be in the original and missing from the copy.\n */\nasync function duplicate(): Promise<void> {\n const row = vacancy.value\n\n if (!row) return\n\n if (dirty.value) await save()\n if (conflict.value || dirty.value) return\n\n working.value = true\n\n try {\n const copy = await api.duplicate(row.id)\n\n toast.success(t('panel.duplicated'))\n // The same route with another id: the editor stays and reads the copy (`watch(id)`).\n await router.push({ path: `${list.value}/${copy.vacancy.id}`, query: { ...route.query } })\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\nfunction badge(): 'default' | 'success' {\n const status = vacancy.value?.status\n\n return status === 'published' || status === 'modified' ? 'success' : 'default'\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(unload: BeforeUnloadEvent): void {\n if (dirty.value) unload.preventDefault()\n}\n\nwatch(current, schedule)\nwatch(id, () => void load())\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n clearTimeout(timer)\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\n/**\n * Leaving flushes the pause rather than asking about it; the question is asked only when the save\n * did not go through — a conflict, a refused field, a server that is not there.\n */\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n await save()\n\n if (!dirty.value) return true\n\n return await confirm({\n title: t('editor.leave-title'),\n message: t('editor.leave-text'),\n confirmText: t('editor.leave'),\n cancelText: t('panel.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the vacancy. On a phone the head folds them into the ···. */\nconst actions = computed<ScreenAction[]>(() => {\n const leads: ScreenAction[] = []\n\n if (previewUrl.value) {\n leads.push({ key: 'preview', label: t('editor.preview'), icon: 'eye', href: previewUrl.value })\n }\n\n if (vacancy.value?.url && vacancy.value.status !== 'draft') {\n leads.push({\n key: 'site',\n label: t('panel.open-on-site'),\n icon: 'link',\n href: vacancy.value.url,\n })\n }\n\n // Throwing away writing lives in the ···, never beside \"publish\", and only while there is a\n // difference between what is written and what is on the site.\n if (vacancy.value?.status === 'modified') {\n leads.push({\n key: 'discard',\n label: t('editor.discard'),\n icon: 'refresh',\n danger: true,\n menu: true,\n run: () => void discard(),\n })\n }\n\n return leads\n})\n</script>\n\n<template>\n <div class=\"wx-vacancy-editor\" @focusout=\"onFocusOut\">\n <template v-if=\"loading || !vacancy\">\n <wx-skeleton class=\"wx-vacancy-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"8\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head\n divider\n :back=\"list\"\n :back-label=\"t('module.vacancies')\"\n :title=\"title || t('editor.untitled')\"\n :subtitle=\"subtitle\"\n :actions=\"actions\"\n >\n <template #trail>\n <wx-breadcrumb size=\"sm\" :label=\"t('editor.trail')\">\n <wx-breadcrumb-item :as=\"'router-link'\" :to=\"list\">\n {{ t('module.vacancies') }}\n </wx-breadcrumb-item>\n <wx-breadcrumb-item current>{{ title || t('editor.untitled') }}</wx-breadcrumb-item>\n </wx-breadcrumb>\n </template>\n\n <template #title-after>\n <wx-badge :type=\"badge()\" dot>{{ t(`panel.status-${vacancy.status}`) }}</wx-badge>\n <wx-badge v-if=\"vacancy.status === 'modified'\" type=\"primary\" round>\n {{ t('panel.edits') }}\n </wx-badge>\n <!-- Closed, and still a page of the site: marked, off the lists (decision 4). -->\n <wx-badge v-if=\"vacancy.closed_reason\" type=\"warning\" round>\n {{ t(`panel.closed-${vacancy.closed_reason}`) }}\n </wx-badge>\n </template>\n </wx-screen-head>\n\n <!-- Somebody else wrote while this editor was typing. Both versions still exist, so the\n question is which one the site gets — a question, not a toast that disappears. -->\n <wx-alert\n v-if=\"conflict\"\n type=\"warning\"\n :title=\"t('editor.conflict-title')\"\n :description=\"conflict.message\"\n >\n <template #actions>\n <wx-button size=\"sm\" variant=\"outline\" @click=\"takeTheirs\">\n {{ t('editor.conflict-theirs') }}\n </wx-button>\n <wx-button size=\"sm\" type=\"primary\" @click=\"keepMine\">\n {{ t('editor.conflict-mine') }}\n </wx-button>\n </template>\n </wx-alert>\n\n <wx-screen v-model=\"values\" name=\"vacancies.form\" :errors=\"errors\" :disabled=\"locked\" />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <!-- The same position in another city is a copy, not a vacancy from nothing (decision 20). -->\n <wx-button\n variant=\"text\"\n :disabled=\"working\"\n :aria-label=\"t('panel.duplicate')\"\n @click=\"duplicate\"\n >\n <template #icon><wx-icon name=\"copy\" /></template>\n <span class=\"wx-vacancy-editor__word\">{{ t('panel.duplicate') }}</span>\n </wx-button>\n\n <wx-button variant=\"outline\" :loading=\"saving\" :disabled=\"!dirty\" @click=\"save\">\n {{ t('editor.save') }}\n </wx-button>\n\n <wx-button\n type=\"primary\"\n :loading=\"working\"\n :disabled=\"vacancy.status === 'published' && !dirty\"\n @click=\"publish\"\n >\n {{ t('panel.publish') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n/*\n * No constructor here, so nothing needs the height of the window: the page scrolls, and the\n * panel gives a screen with an action bar a floor tall enough for the bar to sit at the bottom\n * (`WxMain`, CLAUDE.md §4 on `data-wx-fill`).\n */\n.wx-vacancy-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n min-height: 0;\n container-type: inline-size;\n}\n\n.wx-vacancy-editor__ghost {\n max-width: 420px;\n}\n\n/*\n * On a phone the bar holds the state and three buttons, and with the word the third one pushes\n * the state onto a line of its own — a strip of nothing over the buttons (CLAUDE.md §4 on\n * `WxActionBar`). The copy icon is enough there; the word stays for a screen reader.\n */\n@container (max-width: 480px) {\n .wx-vacancy-editor__word {\n display: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxDate } from '@webx-ui/module-admin'\nimport { confirm, toast, WxBadge, WxButton, WxEmpty, WxSkeleton, WxText } from '@webx-ui/core'\nimport { createVacanciesApi } from './api'\nimport { useVacancyEditor } from './editor'\nimport { useVacanciesMessages } from './i18n'\nimport type { VacancyVersion } from './types'\n\n/**\n * What was published, when, by whom and from where — publications only; the autosaves are\n * insurance, not history.\n *\n * Restoring makes the old version the draft. Publishing it is the same separate step it always\n * is, so nothing here changes the site by itself.\n */\ndefineOptions({ name: 'WxVacancyHistory' })\n\nconst context = useAdmin()\nconst api = createVacanciesApi(context)\nconst editor = useVacancyEditor()\nuseVacanciesMessages()\n\nconst t = useTranslate('webx-vacancies')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\nconst versions = ref<VacancyVersion[] | null>(null)\nconst working = ref(false)\n\nconst vacancy = computed(() => editor?.vacancy.value ?? null)\nconst canManage = computed(() => editor?.canManage ?? false)\n\n/** The newest publication is the site — while the vacancy is on it at all. */\nconst live = computed(() =>\n vacancy.value?.status === 'published' || vacancy.value?.status === 'modified'\n ? (versions.value?.[0]?.number ?? null)\n : null,\n)\n\nasync function load(): Promise<void> {\n const current = vacancy.value\n\n if (!current) return\n\n try {\n versions.value = await api.versions(current.id)\n } catch (error) {\n versions.value = []\n toast.danger(message(error))\n }\n}\n\nasync function restore(version: VacancyVersion): Promise<void> {\n const current = vacancy.value\n\n if (!current) return\n\n const agreed = await confirm({\n title: t('editor.restore-title', { number: version.number }),\n message: t('editor.restore-text'),\n confirmText: t('editor.restore-version'),\n cancelText: t('panel.cancel'),\n })\n\n if (!agreed) return\n\n working.value = true\n\n try {\n await api.restoreVersion(current.id, version.number)\n // Through the editor rather than with the answer: the values it holds are what the form is\n // bound to, and a version restored underneath it has to reach the fields.\n await editor?.reload()\n toast.success(t('editor.restored-version', { number: version.number }))\n } catch (error) {\n toast.danger(message(error))\n } finally {\n working.value = false\n }\n}\n\n// The vacancy is read again after every publication and every restore, so its stamp is the\n// signal that there is something new to list.\nwatch(\n () => [vacancy.value?.id, vacancy.value?.published_at, vacancy.value?.updated_at],\n () => void load(),\n { immediate: true },\n)\n</script>\n\n<template>\n <div class=\"wx-vacancy-history\">\n <wx-skeleton v-if=\"versions === null\" class=\"wx-vacancy-history__ghost\" :rows=\"3\" />\n <wx-empty v-else-if=\"versions.length === 0\" :description=\"t('editor.history-empty')\" />\n <div\n v-for=\"version in versions\"\n v-else\n :key=\"version.number\"\n class=\"wx-vacancy-history__row\"\n :class=\"{ 'is-live': version.number === live }\"\n >\n <code class=\"wx-vacancy-history__number\">{{\n t('editor.version', { number: version.number })\n }}</code>\n <div class=\"wx-vacancy-history__who\">\n <wx-date v-if=\"version.created_at\" :value=\"version.created_at\" size=\"md\" tone=\"default\" />\n <wx-text size=\"sm\" tone=\"muted\">\n {{ version.author ?? t(`editor.source-${version.source}`) }}\n <template v-if=\"version.comment\"> · {{ version.comment }}</template>\n </wx-text>\n </div>\n <wx-badge v-if=\"version.number === live\" type=\"success\" dot>{{\n t('editor.version-live')\n }}</wx-badge>\n <wx-button\n v-if=\"canManage && version.number !== live\"\n size=\"sm\"\n variant=\"outline\"\n :loading=\"working\"\n @click=\"restore(version)\"\n >\n {{ t('editor.restore-version') }}\n </wx-button>\n </div>\n </div>\n</template>\n\n<style scoped>\n.wx-vacancy-history {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--wx-border-default);\n border-radius: var(--wx-radius-md);\n background: var(--wx-bg-surface);\n overflow: hidden;\n}\n\n.wx-vacancy-history__ghost {\n padding: var(--wx-space-10) var(--wx-space-12);\n}\n\n.wx-vacancy-history__row {\n display: flex;\n align-items: center;\n gap: var(--wx-space-12);\n padding: var(--wx-space-10) var(--wx-space-12);\n border-block-end: 1px solid var(--wx-border-default);\n flex-wrap: wrap;\n}\n\n.wx-vacancy-history__row:last-child {\n border-block-end: 0;\n}\n\n.wx-vacancy-history__number {\n width: 40px;\n font-family: var(--wx-font-family-mono);\n font-size: var(--wx-font-size-sm);\n color: var(--wx-text-muted);\n}\n\n.wx-vacancy-history__who {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 160px;\n}\n</style>\n","import { categoryRoutes, type AdminModule, type CategoriesOptions } from '@webx-ui/module-admin'\nimport VacanciesPage from './VacanciesPage.vue'\nimport VacancyEditorPage from './VacancyEditorPage.vue'\nimport VacancyHistory from './VacancyHistory.vue'\n\nexport interface VacanciesOptions {\n /** Where the vacancies live inside the panel. The categories sit under it. */\n path?: string\n}\n\n/**\n * The categories of vacancies as the panel's shared category screens see them. Exported so that a\n * panel mounting the screens somewhere of its own does not have to repeat the words.\n *\n * A category here is a group and a filter, not a page (decision 2): its slug is the key of\n * `?category=` and of `vacancies()->in()`, and the list says \"no page of its own\" where the other\n * modules print an address.\n */\nexport function vacancyCategoriesOptions(path = '/vacancies'): CategoriesOptions {\n return {\n api: 'vacancies/categories',\n path: `${path}/categories`,\n name: 'webx.vacancies.categories',\n module: 'vacancy-categories',\n screen: 'vacancies.category-form',\n manage: 'vacancies.categories.manage',\n count: 'vacancies_count',\n // Every vacancy of the category, closed ones included: \"what is filed here\" is the question.\n items: (id) => ({ path, query: { category: String(id), view: 'all' } }),\n words: {\n new: 'webx-vacancies::category.new',\n empty: 'webx-vacancies::category.empty',\n 'empty-help': 'webx-vacancies::category.empty-help',\n order: 'webx-vacancies::category.order',\n hidden: 'webx-vacancies::category.hidden',\n 'no-address': 'webx-vacancies::category.no-page',\n count: 'webx-vacancies::category.vacancies',\n 'show-items': 'webx-vacancies::category.show-vacancies',\n 'delete-blocked': 'webx-vacancies::category.delete-blocked',\n 'delete-text': 'webx-vacancies::category.delete-text',\n deleted: 'webx-vacancies::category.deleted',\n saved: 'webx-vacancies::category.saved',\n 'field-title': 'webx-vacancies::category.field-title',\n 'field-slug': 'webx-vacancies::category.field-slug',\n },\n }\n}\n\n/**\n * The vacancies as sections of the panel: the vacancies and their categories (§4.10).\n *\n * Two modules rather than one, because the navigation is one entry per module; the server puts\n * them in the `vacancies` group, which is what draws them under one heading. A section whose\n * server half is not installed never appears — the entry is built from the manifest.\n */\nexport function vacancies(options: VacanciesOptions = {}): AdminModule[] {\n const path = options.path ?? '/vacancies'\n\n return [\n {\n id: 'vacancies',\n path,\n routes: [\n { path, name: 'webx.vacancies', component: VacanciesPage, props: { base: path } },\n {\n path: `${path}/:id(\\\\d+)`,\n name: 'webx.vacancies.edit',\n component: VacancyEditorPage,\n props: { base: path },\n },\n ],\n /*\n * The one part of `vacancies.form` only this module can draw. Everything else on it is the\n * panel's (`wx-slug`, `wx-categories`, `wx-relations`, `wx-rich-text`, `wx-repeater`,\n * `wx-date-picker`, `wx-checkbox-group`).\n */\n types: {\n 'wx-vacancy-history': { component: VacancyHistory, kind: 'display' },\n },\n },\n {\n id: 'vacancy-categories',\n path: `${path}/categories`,\n routes: categoryRoutes(vacancyCategoriesOptions(path)),\n },\n ]\n}\n"],"names":["VACANCIES_API","createVacanciesApi","admin","base","data","body","row","id","action","query","search","input","ids","reorderItems","number","DAY","dayOf","value","parts","year","month","day","at","formatDay","locale","now","longDay","vacanciesMessages","employmentKey","code","seeded","useVacanciesMessages","i18n","useAdmin","open","resolve","dismiss","useModal","context","api","t","useTranslate","title","ref","saving","errors","field","useTemplateRef","onMounted","nextTick","submit","error","_createBlock","_unref","WxDialog","$event","_createVNode","WxSpace","WxButton","WxFormItem","_createElementVNode","WxInput","props","__props","route","useRoute","router","useRouter","message","useErrorText","rows","filters","loading","create","createModal","VacancyCreateDialog","canManage","computed","module","view","inBin","state","category","raw","status","q","typed","views","statuses","one","unfiltered","ordered","sortable","hint","emptyText","applied","chips","narrow","chosen","item","load","answer","toast","watch","pause","onBeforeUnmount","name","clearFilters","vacancy","add","made","reorder","live","actionsFor","restore","actions","duplicate","reopen","close","run","remove","copy","confirm","request","said","badge","address","where","city","employment","muted","_openBlock","_createElementBlock","_hoisted_1","WxListScreen","_hoisted_2","WxIcon","WxPopover","WxIndicator","WxAction","_hoisted_3","WxSelect","_cache","_createTextVNode","_hoisted_4","WxFilterChips","WxSkeleton","WxEmpty","_Fragment","WxSortableList","_normalizeClass","_withCtx","_resolveDynamicComponent","_mergeProps","_hoisted_5","_hoisted_6","_toDisplayString","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10","WxTooltip","_hoisted_11","_hoisted_12","_renderList","chip","WxBadge","WxRowMenu","WxAlert","vacancyEditorKey","provideVacancyEditor","editor","provide","useVacancyEditor","inject","PAUSE","locales","useLocales","dates","useDates","list","values","revision","prefix","previewUrl","working","conflict","snapshot","locked","current","dirty","written","localizedValue","subtitle","provideRelationOwner","provideRecordAddress","take","detail","taken","silent","timer","schedule","save","onFocusOut","sending","sent","failure","takeTheirs","keepMine","theirs","discard","publish","leaveGuard","unload","onBeforeRouteLeave","leads","WxCard","WxScreenHead","WxBreadcrumb","WxBreadcrumbItem","WxScreen","WxActionBar","WxSaveState","versions","version","WxDate","WxText","vacancyCategoriesOptions","path","vacancies","options","VacanciesPage","VacancyEditorPage","VacancyHistory","categoryRoutes"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Messages } from '@webx-ui/module-admin';
|
|
2
|
+
/**
|
|
3
|
+
* The English this package speaks on its own, before the server's dictionary arrives, or without
|
|
4
|
+
* one. The server ships the same lines in ten languages under `webx-vacancies::` and overrides
|
|
5
|
+
* these; `messages.test.ts` keeps the two sets of keys equal.
|
|
6
|
+
*
|
|
7
|
+
* `module`, `panel`, `editor` and `category` are the panel's own and match the server's files key
|
|
8
|
+
* for key. `vacancy` is the site's group — the words of the page of a vacancy — and the panel
|
|
9
|
+
* borrows the handful it prints in a row: where, the kind of employment. Only those are here.
|
|
10
|
+
*/
|
|
11
|
+
export declare const vacanciesMessages: Record<string, Messages>;
|
|
12
|
+
/** The word of a schema.org employment type: `FULL_TIME` → `vacancy.employment.FULL_TIME`, as the server names it. */
|
|
13
|
+
export declare function employmentKey(code: string): string;
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AdminModule, CategoriesOptions } from '@webx-ui/module-admin';
|
|
2
|
+
export interface VacanciesOptions {
|
|
3
|
+
/** Where the vacancies live inside the panel. The categories sit under it. */
|
|
4
|
+
path?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The categories of vacancies as the panel's shared category screens see them. Exported so that a
|
|
8
|
+
* panel mounting the screens somewhere of its own does not have to repeat the words.
|
|
9
|
+
*
|
|
10
|
+
* A category here is a group and a filter, not a page (decision 2): its slug is the key of
|
|
11
|
+
* `?category=` and of `vacancies()->in()`, and the list says "no page of its own" where the other
|
|
12
|
+
* modules print an address.
|
|
13
|
+
*/
|
|
14
|
+
export declare function vacancyCategoriesOptions(path?: string): CategoriesOptions;
|
|
15
|
+
/**
|
|
16
|
+
* The vacancies as sections of the panel: the vacancies and their categories (§4.10).
|
|
17
|
+
*
|
|
18
|
+
* Two modules rather than one, because the navigation is one entry per module; the server puts
|
|
19
|
+
* them in the `vacancies` group, which is what draws them under one heading. A section whose
|
|
20
|
+
* server half is not installed never appears — the entry is built from the manifest.
|
|
21
|
+
*/
|
|
22
|
+
export declare function vacancies(options?: VacanciesOptions): AdminModule[];
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.wx-vacancies[data-v-41756f57]{min-width:0;container-type:inline-size}.wx-vacancies__bar[data-v-41756f57]{display:flex;align-items:center;gap:var(--wx-space-8);padding:var(--wx-space-4) var(--wx-space-8) var(--wx-space-8)}.wx-vacancies__search[data-v-41756f57]{flex:1 1 240px;min-width:0;max-width:420px}.wx-vacancies__funnel[data-v-41756f57]{flex:none}.wx-vacancies__filters[data-v-41756f57]{display:flex;flex-direction:column;gap:var(--wx-space-4)}.wx-vacancies__applied[data-v-41756f57]{padding:0 var(--wx-space-8) var(--wx-space-8)}.wx-vacancies__loading[data-v-41756f57]{padding:var(--wx-space-8)}.wx-vacancies__list.is-loading[data-v-41756f57]{opacity:.6}.wx-vacancies__list[data-v-41756f57] .wx-sortable-list__row{padding-inline:var(--wx-space-8)}.wx-vacancies__list[data-v-41756f57] .wx-sortable-list__row:hover{background:var(--wx-bg-subtle);border-radius:var(--wx-radius-sm)}.wx-vacancies__note[data-v-41756f57]{margin-block-start:var(--wx-space-8)}.wx-vacancy-row[data-v-41756f57]{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) 260px;grid-template-areas:"name facts state";align-items:center;gap:var(--wx-space-4) var(--wx-space-16);flex:1 1 auto;min-width:0;padding-block:var(--wx-space-6);color:inherit;text-decoration:none}.wx-vacancy-row__name[data-v-41756f57]{grid-area:name;display:flex;flex-direction:column;min-width:0}.wx-vacancy-row__title[data-v-41756f57]{overflow:hidden;font-weight:var(--wx-font-weight-medium);white-space:nowrap;text-overflow:ellipsis}.wx-vacancy-row__address[data-v-41756f57]{overflow:hidden;color:var(--wx-text-muted);font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);white-space:nowrap;text-overflow:ellipsis}.wx-vacancy-row__facts[data-v-41756f57]{grid-area:facts;display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-2) var(--wx-space-12);min-width:0;color:var(--wx-text-muted);font-size:var(--wx-font-size-sm)}.wx-vacancy-row__where[data-v-41756f57],.wx-vacancy-row__until[data-v-41756f57]{display:inline-flex;align-items:center;gap:var(--wx-space-4);min-width:0;white-space:nowrap}.wx-vacancy-row__employment[data-v-41756f57]{min-width:0}.wx-vacancy-row__state[data-v-41756f57]{grid-area:state;display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:var(--wx-space-4);min-width:0}.wx-vacancy-row.is-closed .wx-vacancy-row__title[data-v-41756f57],.wx-vacancy-row.is-closed .wx-vacancy-row__facts[data-v-41756f57]{color:var(--wx-text-muted)}@container (max-width: 720px){.wx-vacancy-row[data-v-41756f57]{grid-template-columns:minmax(0,1fr);grid-template-areas:"name" "facts" "state";align-items:start}.wx-vacancy-row__state[data-v-41756f57]{justify-content:flex-start}.wx-vacancies__list[data-v-41756f57] .wx-sortable-list__row{gap:var(--wx-space-8);padding-inline:var(--wx-space-4)}.wx-vacancies__bar[data-v-41756f57],.wx-vacancies__applied[data-v-41756f57]{padding-inline:var(--wx-space-4)}.wx-vacancies__search[data-v-41756f57]{max-width:none}}.wx-vacancy-editor[data-v-04fdc324]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));min-height:0;container-type:inline-size}.wx-vacancy-editor__ghost[data-v-04fdc324]{max-width:420px}@container (max-width: 480px){.wx-vacancy-editor__word[data-v-04fdc324]{display:none}}.wx-vacancy-history[data-v-77bc9b70]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-vacancy-history__ghost[data-v-77bc9b70]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-vacancy-history__row[data-v-77bc9b70]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-vacancy-history__row[data-v-77bc9b70]:last-child{border-block-end:0}.wx-vacancy-history__number[data-v-77bc9b70]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-vacancy-history__who[data-v-77bc9b70]{display:flex;flex-direction:column;flex:1;min-width:160px}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ScreenModel } from '@webx-ui/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Never published · on the site · on the site with edits waiting · taken off it — the same four a
|
|
4
|
+
* recipe and an event have (§4.11).
|
|
5
|
+
*/
|
|
6
|
+
export type VacancyStatus = 'draft' | 'published' | 'modified' | 'unpublished';
|
|
7
|
+
/**
|
|
8
|
+
* Which vacancies the list is looking at (decision 13): the open ones — the default, and what the
|
|
9
|
+
* site's lists show — the closed ones, or both.
|
|
10
|
+
*/
|
|
11
|
+
export type VacancyState = 'open' | 'closed' | 'all';
|
|
12
|
+
/**
|
|
13
|
+
* Why a vacancy is closed: somebody closed it (`is_closed`), or its last day is behind us
|
|
14
|
+
* (`valid_through`). When both are true, `manual` is what the server says.
|
|
15
|
+
*/
|
|
16
|
+
export type VacancyClosedReason = 'manual' | 'expired';
|
|
17
|
+
/** Where the work is done (decision 16). */
|
|
18
|
+
export type VacancyWorkplace = 'onsite' | 'remote' | 'hybrid';
|
|
19
|
+
/** A category as a row of the list names one. */
|
|
20
|
+
export interface VacancyTermRef {
|
|
21
|
+
id: number;
|
|
22
|
+
title: string;
|
|
23
|
+
}
|
|
24
|
+
/** One vacancy as the section lists it (§4.11). */
|
|
25
|
+
export interface VacancyRow {
|
|
26
|
+
id: number;
|
|
27
|
+
/** The draft's title in the panel's language, or `#id`. */
|
|
28
|
+
title: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
/** `null` — no address in the language the panel is open in. */
|
|
31
|
+
path: string | null;
|
|
32
|
+
url: string | null;
|
|
33
|
+
workplace: VacancyWorkplace;
|
|
34
|
+
/** In the panel's language, else the default one, else `''`. */
|
|
35
|
+
city: string;
|
|
36
|
+
/** schema.org codes: `FULL_TIME`, `PART_TIME`, … */
|
|
37
|
+
employment_types: string[];
|
|
38
|
+
/** The last day it is open, `YYYY-MM-DD` — a calendar day, not a moment. */
|
|
39
|
+
valid_through: string | null;
|
|
40
|
+
/** `datePosted`, `YYYY-MM-DD`; set by the first publication. */
|
|
41
|
+
posted_at: string | null;
|
|
42
|
+
closed: boolean;
|
|
43
|
+
closed_reason: VacancyClosedReason | null;
|
|
44
|
+
status: VacancyStatus;
|
|
45
|
+
position: number;
|
|
46
|
+
categories: VacancyTermRef[];
|
|
47
|
+
published_at: string | null;
|
|
48
|
+
updated_at: string | null;
|
|
49
|
+
deleted_at: string | null;
|
|
50
|
+
/** What this vacancy was when it was read, so a save can be refused rather than written over. */
|
|
51
|
+
revision?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Every vacancy the query asked for, at once: no paginator, because this is the list where the
|
|
55
|
+
* order is dragged, and a drag cannot cross a page (§4.10).
|
|
56
|
+
*/
|
|
57
|
+
export interface VacanciesList {
|
|
58
|
+
data: VacancyRow[];
|
|
59
|
+
filters: {
|
|
60
|
+
categories: VacancyTermRef[];
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface VacancyQuery {
|
|
64
|
+
state?: VacancyState;
|
|
65
|
+
/** Title or address, in any language the site has. */
|
|
66
|
+
q?: string;
|
|
67
|
+
category?: number | null;
|
|
68
|
+
status?: VacancyStatus | '';
|
|
69
|
+
/** What was deleted. `state` does not apply there. */
|
|
70
|
+
trashed?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface VacancyInput {
|
|
73
|
+
title: string;
|
|
74
|
+
slug?: string;
|
|
75
|
+
}
|
|
76
|
+
/** The values of `vacancies.form` keyed by field name, and the revision they were read at. */
|
|
77
|
+
export interface VacancySave {
|
|
78
|
+
values: ScreenModel;
|
|
79
|
+
revision?: string;
|
|
80
|
+
}
|
|
81
|
+
/** One vacancy as its editor opens it. */
|
|
82
|
+
export interface VacancyDetail {
|
|
83
|
+
vacancy: VacancyRow;
|
|
84
|
+
values: ScreenModel;
|
|
85
|
+
revision: string;
|
|
86
|
+
/** The first segment of every vacancy address (`webx-vacancies.prefix`); never empty. */
|
|
87
|
+
prefix: string;
|
|
88
|
+
/** `null` without `module-blocks`, which owns the preview by token. */
|
|
89
|
+
preview_url: string | null;
|
|
90
|
+
}
|
|
91
|
+
/** A 409: somebody wrote in between. `data` is the vacancy as it now is. */
|
|
92
|
+
export interface VacancyConflict {
|
|
93
|
+
message: string;
|
|
94
|
+
data: VacancyDetail;
|
|
95
|
+
}
|
|
96
|
+
/** One publication in the history. */
|
|
97
|
+
export interface VacancyVersion {
|
|
98
|
+
number: number;
|
|
99
|
+
created_at: string | null;
|
|
100
|
+
author: string | null;
|
|
101
|
+
source: 'panel' | 'mcp' | 'import' | string;
|
|
102
|
+
comment: string | null;
|
|
103
|
+
is_pinned: boolean;
|
|
104
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webx-ui/module-vacancies",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vacancies for the WebX UI admin panel: who the organisation is looking for — where and how, the terms and the pay, the duties, the requirements and what is offered — in flat categories, closed by hand or by date, with a response form from the inbox.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"*.css"
|
|
9
|
+
],
|
|
10
|
+
"keywords": [
|
|
11
|
+
"webx-ui",
|
|
12
|
+
"vue",
|
|
13
|
+
"admin",
|
|
14
|
+
"cms",
|
|
15
|
+
"vacancies"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/webx-ui/webx-ui.git",
|
|
20
|
+
"directory": "packages/module-vacancies"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://webx-ui.github.io/webx-ui/guide/vacancies.html",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./style.css": "./dist/style.css",
|
|
38
|
+
"./dist/style.css": "./dist/style.css",
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"vue": "^3.5.0",
|
|
43
|
+
"vue-router": "^4.5.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@webx-ui/core": "^0.34.0",
|
|
47
|
+
"@webx-ui/module-admin": "^0.18.1",
|
|
48
|
+
"@webx-ui/schema": "^0.6.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^24.10.1",
|
|
52
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"vite": "^7.1.14",
|
|
55
|
+
"vite-plugin-dts": "^4.5.4",
|
|
56
|
+
"vue": "^3.5.24",
|
|
57
|
+
"vue-router": "^4.5.1",
|
|
58
|
+
"vue-tsc": "^3.1.3"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "vite build",
|
|
62
|
+
"dev": "vite build --watch",
|
|
63
|
+
"typecheck": "vue-tsc -p tsconfig.json --noEmit"
|
|
64
|
+
}
|
|
65
|
+
}
|