add application number

This commit is contained in:
Владимир Фёдоров 2026-03-16 01:56:50 +07:00
parent 3a30123096
commit 825d330056
11 changed files with 99 additions and 12 deletions

View File

@ -139,6 +139,7 @@ message Application {
int64 id = 1;
string name = 2;
string state = 3;
string number = 4;
}
message Door {

View File

@ -1 +1 @@
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64);--main-color: rgba(34, 50, 60, 1);--second-color: rgb(97, 74, 22);--main-back-color: rgba(240, 240, 240, 1);--main-back-item-color: rgba(254, 254, 254, 1)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100dvh;color:var(--color-text);background:var(--main-back-color);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.header-block{height:60px;background-color:var(--main-color);font-size:large;color:#fff;vertical-align:middle;padding:15px 0 10px 16px;font-weight:700}.input-custom{width:100%;box-sizing:border-box;margin-bottom:15px}.button-custom{margin-left:auto;background-color:var(--main-color);font-weight:600;color:#fff}.button-custom:hover{background-color:var(--main-color);opacity:.9}.button-custom:disabled{opacity:.5}.button-dialog{background-color:var(--main-color);font-weight:600;color:#fff;padding:6px 14px;border:1px solid #ddd;border-radius:15px;font-size:16px;margin-right:6px}.button-dialog:hover{background-color:var(--main-color);opacity:.9}.button-dialog:disabled{opacity:.5}.input-custom,.button-custom{padding:12px 16px;border:1px solid #ddd;border-radius:15px;font-size:16px}.button-container{display:flex}.center-message{display:flex;justify-content:center;align-items:center;height:calc(100dvh - 100px);text-align:center}header[data-v-913ef6b1]{line-height:1.5;max-height:100vh}.logo[data-v-913ef6b1]{display:block;margin:0 auto 2rem}nav[data-v-913ef6b1]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-913ef6b1]{color:var(--color-text)}nav a.router-link-exact-active[data-v-913ef6b1]:hover{background-color:transparent}nav a[data-v-913ef6b1]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-913ef6b1]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-913ef6b1]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-913ef6b1]{margin:0 2rem 0 0}header .wrapper[data-v-913ef6b1]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-913ef6b1]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}body[data-v-127d6c85]{overflow:hidden}.hr[data-v-127d6c85]{margin:7px 0}.body-custom[data-v-127d6c85]{font-size:medium}.info-custom[data-v-127d6c85]{padding-left:15px}.logo[data-v-127d6c85]{float:left;margin:10px}.logo-right[data-v-127d6c85]{float:right;margin:12px}.second-color[data-v-127d6c85]{color:var(--second-color)}.form-custom[data-v-127d6c85]{border:1px solid #444444;background-color:var(--main-back-color);position:fixed;bottom:0;left:0;width:100%;padding:20px;color:#fff}.message-cloud[data-v-127d6c85]{border:1px solid #444444;border-radius:15px;margin:12px 10px;padding:16px;background-color:var(--main-back-item-color);display:flow-root}.message-header[data-v-127d6c85]{font-size:large;font-weight:200}.message-content[data-v-127d6c85]{font-weight:500;white-space:pre-wrap}.message-image[data-v-127d6c85]{width:40%;float:left;margin-right:15px}.message-footer[data-v-127d6c85]{font-weight:400;color:var(--second-color)}.form-block[data-v-127d6c85]{height:140px}.messages-block[data-v-127d6c85]{height:calc(100dvh - 200px);overflow-y:auto;scrollbar-width:none}@media (min-width: 1025px){.center-block-custom[data-v-127d6c85]{width:700px;margin:0 auto}}.center-message[data-v-127d6c85]{height:calc(100dvh - 140px)}.qr[data-v-127d6c85]{text-align:center;width:200px}.collapse-icon[data-v-127d6c85]{padding:0 15px;cursor:pointer}.team-name-block[data-v-127d6c85]{float:right;padding:0 20px}.text-truncate[data-v-127d6c85]{width:100px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:2px 7px;margin:0 20px;background:#284557;border-radius:4px;font-size:medium}.error-message[data-v-13746d20]{color:brown;margin:16px 0}
:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64);--main-color: rgba(34, 50, 60, 1);--second-color: rgb(97, 74, 22);--main-back-color: rgba(240, 240, 240, 1);--main-back-item-color: rgba(254, 254, 254, 1)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100dvh;color:var(--color-text);background:var(--main-back-color);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.header-block{height:60px;background-color:var(--main-color);font-size:large;color:#fff;vertical-align:middle;padding:15px 0 10px 16px;font-weight:700}.input-custom{width:100%;box-sizing:border-box;margin-bottom:15px}.button-custom{margin-left:auto;background-color:var(--main-color);font-weight:600;color:#fff}.button-custom:hover{background-color:var(--main-color);opacity:.9}.button-custom:disabled{opacity:.5}.button-dialog{background-color:var(--main-color);font-weight:600;color:#fff;padding:6px 14px;border:1px solid #ddd;border-radius:15px;font-size:16px;margin-right:6px}.button-dialog:hover{background-color:var(--main-color);opacity:.9}.button-dialog:disabled{opacity:.5}.input-custom,.button-custom{padding:12px 16px;border:1px solid #ddd;border-radius:15px;font-size:16px}.button-container{display:flex}.center-message{display:flex;justify-content:center;align-items:center;height:calc(100dvh - 100px);text-align:center}header[data-v-913ef6b1]{line-height:1.5;max-height:100vh}.logo[data-v-913ef6b1]{display:block;margin:0 auto 2rem}nav[data-v-913ef6b1]{width:100%;font-size:12px;text-align:center;margin-top:2rem}nav a.router-link-exact-active[data-v-913ef6b1]{color:var(--color-text)}nav a.router-link-exact-active[data-v-913ef6b1]:hover{background-color:transparent}nav a[data-v-913ef6b1]{display:inline-block;padding:0 1rem;border-left:1px solid var(--color-border)}nav a[data-v-913ef6b1]:first-of-type{border:0}@media (min-width: 1024px){header[data-v-913ef6b1]{display:flex;place-items:center;padding-right:calc(var(--section-gap) / 2)}.logo[data-v-913ef6b1]{margin:0 2rem 0 0}header .wrapper[data-v-913ef6b1]{display:flex;place-items:flex-start;flex-wrap:wrap}nav[data-v-913ef6b1]{text-align:left;margin-left:-1rem;font-size:1rem;padding:1rem 0;margin-top:1rem}}body[data-v-95a9ee2d]{overflow:hidden}.hr[data-v-95a9ee2d]{margin:7px 0}.body-custom[data-v-95a9ee2d]{font-size:medium}.info-custom[data-v-95a9ee2d]{padding-left:15px}.logo[data-v-95a9ee2d]{float:left;margin:10px}.logo-right[data-v-95a9ee2d]{float:right;margin:12px}.second-color[data-v-95a9ee2d]{color:var(--second-color)}.form-custom[data-v-95a9ee2d]{border:1px solid #444444;background-color:var(--main-back-color);position:fixed;bottom:0;left:0;width:100%;padding:20px;color:#fff}.message-cloud[data-v-95a9ee2d]{border:1px solid #444444;border-radius:15px;margin:12px 10px;padding:16px;background-color:var(--main-back-item-color);display:flow-root}.message-header[data-v-95a9ee2d]{font-size:large;font-weight:200}.message-content[data-v-95a9ee2d]{font-weight:500;white-space:pre-wrap}.message-image[data-v-95a9ee2d]{width:40%;float:left;margin-right:15px}.message-footer[data-v-95a9ee2d]{font-weight:400;color:var(--second-color)}.form-block[data-v-95a9ee2d]{height:140px}.messages-block[data-v-95a9ee2d]{height:calc(100dvh - 200px);overflow-y:auto;scrollbar-width:none}@media (min-width: 1025px){.center-block-custom[data-v-95a9ee2d]{width:700px;margin:0 auto}}.center-message[data-v-95a9ee2d]{height:calc(100dvh - 140px)}.qr[data-v-95a9ee2d]{text-align:center;width:200px}.collapse-icon[data-v-95a9ee2d]{padding:0 15px;cursor:pointer}.team-name-block[data-v-95a9ee2d]{float:right;padding:0 20px}.text-truncate[data-v-95a9ee2d]{width:100px;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:2px 7px;margin:0 20px;background:#284557;border-radius:4px;font-size:medium}.error-message[data-v-13746d20]{color:brown;margin:16px 0}

View File

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Вечерний детектив</title>
<script type="module" crossorigin src="/assets/index-bGblozls.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BJOT4xui.css">
<script type="module" crossorigin src="/assets/index-DO4eu8bM.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CsH95clG.css">
</head>
<body>
<div id="app"></div>

View File

@ -20,5 +20,8 @@ func (s *service) GetTeamClientLink(name string, password string) string {
}
func (s *service) GetImageLink(name string) string {
if len(name) == 0 {
return ""
}
return fmt.Sprintf("%s/%s", s.host, name)
}

View File

@ -38,6 +38,7 @@ func mapPlaceToProtoAction(place *story_service_models.Place) *proto.Action {
func mapStoryApplicationToProtoApplication(application *story_service_models.Application) *proto.Application {
return &proto.Application{
Name: application.Name,
Number: application.Number,
}
}

View File

@ -1,11 +1,34 @@
package models
import "fmt"
type Application struct {
Name string `json:"name"`
Number string `json:"-"`
}
func NewApplication(name string) *Application {
return &Application{
func NewApplication(
name string,
opts ...ApplicationOpt,
) *Application {
application := &Application{
Name: name,
}
for _, opt := range opts {
opt(application)
}
return application
}
type ApplicationOpt func(application *Application) error
func WithApplicationNumber(number int) ApplicationOpt {
return func(application *Application) error {
application.SetNumber(number)
return nil
}
}
func (a *Application) SetNumber(number int) {
a.Number = fmt.Sprintf("%d", number)
}

View File

@ -95,8 +95,21 @@ func (s *StoryService) GetPlaces(codes []string) []*models.Place {
places := make([]*models.Place, 0, 100)
mOpen := map[string]any{}
mDeleted := map[string]any{}
applicationNumber := 1
applicationsMap := make(map[string]interface{}, 10)
for i, code := range codes {
place := s.GetPlace(code)
for i, application := range place.Applications {
if _, ok := applicationsMap[application.Name]; ok {
place.Applications = append(place.Applications[:i], place.Applications[i+1:]...)
if len(place.Applications) == 0 {
place.Applications = nil
}
}
applicationsMap[application.Name] = struct{}{}
application.SetNumber(applicationNumber)
applicationNumber++
}
if _, ok := mOpen[place.Code]; place.Hidden && !ok {
place = models.NewNotFoundPlace(place.Code)
}

View File

@ -456,6 +456,40 @@ func TestStoryService_GetPlaces(t *testing.T) {
models.NewNotFoundPlace("Ы-2"),
},
},
{
name: "Улики не повторяются",
story: &models.Story{
Places: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication("Название"),
),
),
},
},
codes: []string{"Ы", "Ы"},
want: []*models.Place{
models.NewPlace(
"Ы",
"Название",
"Текст",
models.WithPlaceApplication(
models.NewApplication(
"Название",
models.WithApplicationNumber(1),
),
),
),
models.NewPlace(
"Ы",
"Название",
"Текст",
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -535,6 +535,7 @@ type Application struct {
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"`
Number string `protobuf:"bytes,4,opt,name=number,proto3" json:"number,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -590,6 +591,13 @@ func (x *Application) GetState() string {
return ""
}
func (x *Application) GetNumber() string {
if x != nil {
return x.Number
}
return ""
}
type Door struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
@ -1736,11 +1744,12 @@ const file_main_proto_rawDesc = "" +
"\bpassword\x18\x03 \x01(\tR\bpassword\x12\x10\n" +
"\x03url\x18\x04 \x01(\tR\x03url\x12\x1c\n" +
"\tspendTime\x18\x05 \x01(\x03R\tspendTime\x12H\n" +
"\fapplications\x18\x06 \x03(\v2$.crabs.evening_detective.ApplicationR\fapplications\"G\n" +
"\fapplications\x18\x06 \x03(\v2$.crabs.evening_detective.ApplicationR\fapplications\"_\n" +
"\vApplication\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" +
"\x05state\x18\x03 \x01(\tR\x05state\"B\n" +
"\x05state\x18\x03 \x01(\tR\x05state\x12\x16\n" +
"\x06number\x18\x04 \x01(\tR\x06number\"B\n" +
"\x04Door\x12\x12\n" +
"\x04code\x18\x01 \x01(\tR\x04code\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +

View File

@ -462,6 +462,9 @@
},
"state": {
"type": "string"
},
"number": {
"type": "string"
}
}
},