Rename compose file to docker-compose.yml

This commit is contained in:
Ruslan Bakiev
2026-02-19 12:58:24 +07:00
parent 3ac487c25b
commit cd70c57a3b
4 changed files with 185 additions and 13 deletions

View File

@@ -1487,6 +1487,7 @@ const peopleSortOptions: Array<{ value: PeopleSortMode; label: string }> = [
{ value: "country", label: "Country" },
];
const selectedDealId = ref(deals.value[0]?.id ?? "");
const selectedDealStepsExpanded = ref(false);
const commThreads = computed(() => {
const sorted = [...commItems.value].sort((a, b) => a.at.localeCompare(b.at));
@@ -1803,6 +1804,22 @@ function formatDealHeadline(deal: Deal) {
return `${title} за ${amountRaw}`;
}
function getDealCurrentStep(deal: Deal) {
if (!deal.steps?.length) return null;
if (deal.currentStepId) {
const explicit = deal.steps.find((step) => step.id === deal.currentStepId);
if (explicit) return explicit;
}
const inProgress = deal.steps.find((step) => step.status === "in_progress");
if (inProgress) return inProgress;
const nextTodo = deal.steps.find((step) => step.status !== "done");
return nextTodo ?? deal.steps[deal.steps.length - 1];
}
function getDealCurrentStepLabel(deal: Deal) {
return getDealCurrentStep(deal)?.title?.trim() || deal.nextStep.trim() || deal.stage.trim() || "Без шага";
}
function parseDateFromText(input: string) {
const text = input.trim();
if (!text) return null;
@@ -1847,11 +1864,33 @@ function formatDealDeadline(dueDate: Date) {
return `через ${dayDiff} ${pluralizeRuDays(dayDiff)}`;
}
function isDealStepDone(step: DealStep) {
return step.status === "done";
}
function formatDealStepMeta(step: DealStep) {
if (step.status === "done") return "выполнено";
if (step.status === "blocked") return "заблокировано";
if (!step.dueAt) {
if (step.status === "in_progress") return "в работе";
return "без дедлайна";
}
const parsed = new Date(step.dueAt);
if (Number.isNaN(parsed.getTime())) return "без дедлайна";
return formatDealDeadline(parsed);
}
const selectedWorkspaceDealDueDate = computed(() => {
const deal = selectedWorkspaceDeal.value;
if (!deal) return null;
const fromNextStep = parseDateFromText(deal.nextStep);
const currentStep = getDealCurrentStep(deal);
if (currentStep?.dueAt) {
const parsed = new Date(currentStep.dueAt);
if (!Number.isNaN(parsed.getTime())) return parsed;
}
const fromNextStep = parseDateFromText(currentStep?.title || deal.nextStep);
if (fromNextStep) return fromNextStep;
const now = Date.now();
@@ -1870,12 +1909,25 @@ const selectedWorkspaceDealDueDate = computed(() => {
const selectedWorkspaceDealSubtitle = computed(() => {
const deal = selectedWorkspaceDeal.value;
if (!deal) return "";
const stepLabel = deal.nextStep.trim() || deal.stage.trim() || "Без шага";
const stepLabel = getDealCurrentStepLabel(deal);
const dueDate = selectedWorkspaceDealDueDate.value;
if (!dueDate) return `${stepLabel} · без дедлайна`;
return `${stepLabel} · ${formatDealDeadline(dueDate)}`;
});
const selectedWorkspaceDealSteps = computed(() => {
const deal = selectedWorkspaceDeal.value;
if (!deal?.steps?.length) return [];
return [...deal.steps].sort((a, b) => a.order - b.order);
});
watch(
() => selectedWorkspaceDeal.value?.id ?? "",
() => {
selectedDealStepsExpanded.value = false;
},
);
async function transcribeCallItem(item: CommItem) {
const itemId = item.id;
if (callTranscriptLoading.value[itemId]) return;
@@ -1964,6 +2016,7 @@ function pushPilotNote(text: string) {
function openCommunicationThread(contact: string) {
selectedTab.value = "communications";
peopleLeftMode.value = "contacts";
selectedDealStepsExpanded.value = false;
const linkedContact = contacts.value.find((item) => item.name === contact);
if (linkedContact) {
selectedContactId.value = linkedContact.id;
@@ -1980,6 +2033,7 @@ function openCommunicationThread(contact: string) {
function openDealThread(deal: Deal) {
selectedDealId.value = deal.id;
selectedDealStepsExpanded.value = false;
openCommunicationThread(deal.contact);
}
@@ -2955,7 +3009,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
<span class="shrink-0 text-[10px] text-base-content/55">{{ deal.amount }}</span>
</div>
<p class="mt-0.5 truncate text-[11px] text-base-content/75">{{ deal.company }} · {{ deal.stage }}</p>
<p class="mt-0.5 truncate text-[11px] text-base-content/60">{{ deal.nextStep }}</p>
<p class="mt-0.5 truncate text-[11px] text-base-content/60">{{ getDealCurrentStepLabel(deal) }}</p>
</button>
<p v-if="peopleListMode === 'contacts' && peopleContactList.length === 0" class="px-1 py-2 text-xs text-base-content/55">
@@ -3421,6 +3475,33 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
<p class="mt-1 text-[11px] text-base-content/75">
{{ selectedWorkspaceDealSubtitle }}
</p>
<button
v-if="selectedWorkspaceDealSteps.length"
class="mt-2 text-[11px] font-medium text-primary hover:underline"
@click="selectedDealStepsExpanded = !selectedDealStepsExpanded"
>
{{ selectedDealStepsExpanded ? "Скрыть шаги" : `Показать шаги (${selectedWorkspaceDealSteps.length})` }}
</button>
<div v-if="selectedDealStepsExpanded && selectedWorkspaceDealSteps.length" class="mt-2 space-y-1.5">
<div
v-for="step in selectedWorkspaceDealSteps"
:key="step.id"
class="flex items-start gap-2 rounded-lg border border-base-300/70 bg-base-100/80 px-2 py-1.5"
>
<input
type="checkbox"
class="checkbox checkbox-xs mt-0.5"
:checked="isDealStepDone(step)"
disabled
>
<div class="min-w-0 flex-1">
<p class="truncate text-[11px] font-medium" :class="isDealStepDone(step) ? 'line-through text-base-content/60' : 'text-base-content/90'">
{{ step.title }}
</p>
<p class="mt-0.5 text-[10px] text-base-content/55">{{ formatDealStepMeta(step) }}</p>
</div>
</div>
</div>
</div>
<div>