diff --git a/dist/assets/UsersView-DFrrjAyr.css b/dist/assets/UsersView-DFrrjAyr.css
deleted file mode 100644
index 41c8968..0000000
--- a/dist/assets/UsersView-DFrrjAyr.css
+++ /dev/null
@@ -1 +0,0 @@
-.user-block[data-v-248d8ad2]{background-color:#222;border-radius:10px;margin:0 0 20px;padding:20px}.user-field[data-v-248d8ad2]{margin:10px}.roles-buttons-block[data-v-248d8ad2]{margin-top:20px}
diff --git a/dist/index.html b/dist/index.html
index 80348be..ee26b9a 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -5,10 +5,10 @@
Вечерний детектив
-
-
-
-
+
+
+
+
diff --git a/proto/main.proto b/proto/main.proto
index 9cf2a10..915aebc 100644
--- a/proto/main.proto
+++ b/proto/main.proto
@@ -41,6 +41,54 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
};
service EveningDetectiveServer {
+ rpc GetTasks(GetTasksReq) returns (GetTasksRsp) {
+ option (google.api.http) = { get: "/api/tasks" };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "Задачи";
+ summary: "Получить общую доску задач";
+ description: "Доступно разработчикам и администратору.";
+ };
+ }
+ rpc GetTaskAssignees(GetTaskAssigneesReq) returns (GetTaskAssigneesRsp) {
+ option (google.api.http) = { get: "/api/tasks/assignees" };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "Задачи";
+ summary: "Получить разработчиков для назначения";
+ description: "Доступно разработчикам и администратору.";
+ };
+ }
+ rpc CreateTask(CreateTaskReq) returns (TaskRsp) {
+ option (google.api.http) = { post: "/api/tasks" body: "*" };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "Задачи";
+ summary: "Создать задачу";
+ description: "Доступно разработчикам и администратору.";
+ };
+ }
+ rpc UpdateTask(UpdateTaskReq) returns (TaskRsp) {
+ option (google.api.http) = { put: "/api/tasks/{ID}" body: "*" };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "Задачи";
+ summary: "Редактировать задачу";
+ description: "Доступно разработчикам и администратору.";
+ };
+ }
+ rpc SetTaskStatus(SetTaskStatusReq) returns (TaskRsp) {
+ option (google.api.http) = { post: "/api/tasks/{ID}/status" body: "*" };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "Задачи";
+ summary: "Изменить статус задачи";
+ description: "Доступно разработчикам и администратору.";
+ };
+ }
+ rpc DeleteTask(DeleteTaskReq) returns (DeleteTaskRsp) {
+ option (google.api.http) = { post: "/api/tasks/{ID}/delete" body: "*" };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ tags: "Задачи";
+ summary: "Удалить задачу";
+ description: "Доступно разработчикам и администратору.";
+ };
+ }
rpc GetParticipation(GetParticipationReq) returns (ParticipationRsp) {
option (google.api.http) = { post: "/api/participation/get" body: "*" };
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
@@ -2188,3 +2236,56 @@ message TicketPage {
optional google.protobuf.Timestamp used_at = 7;
string used_by = 8;
}
+
+// Developer tasks use new, in_progress, review and done statuses.
+message DeveloperTask {
+ int32 ID = 1;
+ string title = 2;
+ string description = 3;
+ string status = 4;
+ // Omitted assignee means the task is unassigned.
+ optional int32 assigneeID = 5;
+ string assigneeName = 6;
+ optional int32 authorID = 7;
+ // Calendar deadline in YYYY-MM-DD format; empty means no deadline.
+ string dueDate = 8;
+ google.protobuf.Timestamp createdAt = 9;
+ google.protobuf.Timestamp updatedAt = 10;
+ // Required for updates and deletion to detect concurrent edits.
+ int32 version = 11;
+}
+message TaskAssignee {
+ int32 ID = 1;
+ string name = 2;
+}
+message GetTasksReq {}
+message GetTasksRsp { repeated DeveloperTask tasks = 1; }
+message GetTaskAssigneesReq {}
+message GetTaskAssigneesRsp { repeated TaskAssignee assignees = 1; }
+message CreateTaskReq {
+ string title = 1;
+ string description = 2;
+ string status = 3;
+ optional int32 assigneeID = 4;
+ string dueDate = 5;
+}
+message UpdateTaskReq {
+ int32 ID = 1;
+ string title = 2;
+ string description = 3;
+ string status = 4;
+ optional int32 assigneeID = 5;
+ string dueDate = 6;
+ int32 version = 7;
+}
+message SetTaskStatusReq {
+ int32 ID = 1;
+ string status = 2;
+ int32 version = 3;
+}
+message DeleteTaskReq {
+ int32 ID = 1;
+ int32 version = 2;
+}
+message TaskRsp { DeveloperTask task = 1; }
+message DeleteTaskRsp {}
diff --git a/src/api/generated/crabs/evening_detective_server/index.ts b/src/api/generated/crabs/evening_detective_server/index.ts
index bb93276..36c380a 100644
--- a/src/api/generated/crabs/evening_detective_server/index.ts
+++ b/src/api/generated/crabs/evening_detective_server/index.ts
@@ -1111,7 +1111,86 @@ export type AcceptTicketRsp = {
ticket: TicketPage | undefined;
};
+// Developer tasks use new, in_progress, review and done statuses.
+export type DeveloperTask = {
+ ID: number | undefined;
+ title: string | undefined;
+ description: string | undefined;
+ status: string | undefined;
+ // Omitted assignee means the task is unassigned.
+ assigneeID?: number;
+ assigneeName: string | undefined;
+ authorID?: number;
+ // Calendar deadline in YYYY-MM-DD format; empty means no deadline.
+ dueDate: string | undefined;
+ createdAt: wellKnownTimestamp | undefined;
+ updatedAt: wellKnownTimestamp | undefined;
+ // Required for updates and deletion to detect concurrent edits.
+ version: number | undefined;
+};
+
+export type TaskAssignee = {
+ ID: number | undefined;
+ name: string | undefined;
+};
+
+export type GetTasksReq = {
+};
+
+export type GetTasksRsp = {
+ tasks: DeveloperTask[] | undefined;
+};
+
+export type GetTaskAssigneesReq = {
+};
+
+export type GetTaskAssigneesRsp = {
+ assignees: TaskAssignee[] | undefined;
+};
+
+export type CreateTaskReq = {
+ title: string | undefined;
+ description: string | undefined;
+ status: string | undefined;
+ assigneeID?: number;
+ dueDate: string | undefined;
+};
+
+export type UpdateTaskReq = {
+ ID: number | undefined;
+ title: string | undefined;
+ description: string | undefined;
+ status: string | undefined;
+ assigneeID?: number;
+ dueDate: string | undefined;
+ version: number | undefined;
+};
+
+export type SetTaskStatusReq = {
+ ID: number | undefined;
+ status: string | undefined;
+ version: number | undefined;
+};
+
+export type DeleteTaskReq = {
+ ID: number | undefined;
+ version: number | undefined;
+};
+
+export type TaskRsp = {
+ task: DeveloperTask | undefined;
+};
+
+export type DeleteTaskRsp = {
+};
+
export interface EveningDetectiveServer {
+ GetTasks(request: GetTasksReq): Promise;
+ GetTaskAssignees(request: GetTaskAssigneesReq): Promise;
+ CreateTask(request: CreateTaskReq): Promise;
+ UpdateTask(request: UpdateTaskReq): Promise;
+ SetTaskStatus(request: SetTaskStatusReq): Promise;
+ DeleteTask(request: DeleteTaskReq): Promise;
GetParticipation(request: GetParticipationReq): Promise;
AnswerParticipation(request: AnswerParticipationReq): Promise;
Ping(request: PingReq): Promise;
@@ -1234,6 +1313,117 @@ export function createEveningDetectiveServerClient(
handler: RequestHandler
): EveningDetectiveServer {
return {
+ GetTasks(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
+ const path = `api/tasks`; // eslint-disable-line quotes
+ const body = null;
+ const queryParams: string[] = [];
+ let uri = path;
+ if (queryParams.length > 0) {
+ uri += `?${queryParams.join("&")}`
+ }
+ return handler({
+ path: uri,
+ method: "GET",
+ body,
+ }, {
+ service: "EveningDetectiveServer",
+ method: "GetTasks",
+ }) as Promise;
+ },
+ GetTaskAssignees(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
+ const path = `api/tasks/assignees`; // eslint-disable-line quotes
+ const body = null;
+ const queryParams: string[] = [];
+ let uri = path;
+ if (queryParams.length > 0) {
+ uri += `?${queryParams.join("&")}`
+ }
+ return handler({
+ path: uri,
+ method: "GET",
+ body,
+ }, {
+ service: "EveningDetectiveServer",
+ method: "GetTaskAssignees",
+ }) as Promise;
+ },
+ CreateTask(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
+ const path = `api/tasks`; // eslint-disable-line quotes
+ const body = JSON.stringify(request);
+ const queryParams: string[] = [];
+ let uri = path;
+ if (queryParams.length > 0) {
+ uri += `?${queryParams.join("&")}`
+ }
+ return handler({
+ path: uri,
+ method: "POST",
+ body,
+ }, {
+ service: "EveningDetectiveServer",
+ method: "CreateTask",
+ }) as Promise;
+ },
+ UpdateTask(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
+ if (!request.ID) {
+ throw new Error("missing required field request.ID");
+ }
+ const path = `api/tasks/${request.ID}`; // eslint-disable-line quotes
+ const body = JSON.stringify(request);
+ const queryParams: string[] = [];
+ let uri = path;
+ if (queryParams.length > 0) {
+ uri += `?${queryParams.join("&")}`
+ }
+ return handler({
+ path: uri,
+ method: "PUT",
+ body,
+ }, {
+ service: "EveningDetectiveServer",
+ method: "UpdateTask",
+ }) as Promise;
+ },
+ SetTaskStatus(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
+ if (!request.ID) {
+ throw new Error("missing required field request.ID");
+ }
+ const path = `api/tasks/${request.ID}/status`; // eslint-disable-line quotes
+ const body = JSON.stringify(request);
+ const queryParams: string[] = [];
+ let uri = path;
+ if (queryParams.length > 0) {
+ uri += `?${queryParams.join("&")}`
+ }
+ return handler({
+ path: uri,
+ method: "POST",
+ body,
+ }, {
+ service: "EveningDetectiveServer",
+ method: "SetTaskStatus",
+ }) as Promise;
+ },
+ DeleteTask(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
+ if (!request.ID) {
+ throw new Error("missing required field request.ID");
+ }
+ const path = `api/tasks/${request.ID}/delete`; // eslint-disable-line quotes
+ const body = JSON.stringify(request);
+ const queryParams: string[] = [];
+ let uri = path;
+ if (queryParams.length > 0) {
+ uri += `?${queryParams.join("&")}`
+ }
+ return handler({
+ path: uri,
+ method: "POST",
+ body,
+ }, {
+ service: "EveningDetectiveServer",
+ method: "DeleteTask",
+ }) as Promise;
+ },
GetParticipation(request) { // eslint-disable-line @typescript-eslint/no-unused-vars
const path = `api/participation/get`; // eslint-disable-line quotes
const body = JSON.stringify(request);
diff --git a/src/components/HeaderMenu.vue b/src/components/HeaderMenu.vue
index ec6ff36..d8e1b1e 100644
--- a/src/components/HeaderMenu.vue
+++ b/src/components/HeaderMenu.vue
@@ -112,6 +112,15 @@ const openMenu = ref(false)
>
Сценарии
+